diff --git a/src/app/group-monitor/backend.service.ts b/src/app/group-monitor/backend.service.ts
index 504ff6c0331fb2e328fce33dd7432bd7fd636ca2..00e769c1e3956621f086b8482866e50dbee0f5e7 100644
--- a/src/app/group-monitor/backend.service.ts
+++ b/src/app/group-monitor/backend.service.ts
@@ -1,24 +1,25 @@
 import { Injectable } from '@angular/core';
 import { HttpHeaders } from '@angular/common/http';
-import { Observable, of, Subscription } from 'rxjs';
-import { catchError } from 'rxjs/operators';
-
-import { BookletError, GroupData, TestSessionData } from './group-monitor.interfaces';
+import { Observable, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+import {
+  BookletError, CommandResponse, GroupData, TestSessionData
+} from './group-monitor.interfaces';
 import { WebsocketBackendService } from '../shared/websocket-backend.service';
 import { ApiError } from '../app.interfaces';
 
 @Injectable()
 export class BackendService extends WebsocketBackendService<TestSessionData[]> {
-  public pollingEndpoint = '/monitor/test-sessions';
-  public pollingInterval = 5000;
-  public wsChannelName = 'test-sessions';
-  public initialData: TestSessionData[] = [];
+  pollingEndpoint = '/monitor/test-sessions';
+  pollingInterval = 5000;
+  wsChannelName = 'test-sessions';
+  initialData: TestSessionData[] = [];
 
-  public observeSessionsMonitor(): Observable<TestSessionData[]> {
+  observeSessionsMonitor(): Observable<TestSessionData[]> {
     return this.observeEndpointAndChannel();
   }
 
-  public getBooklet(bookletName: string): Observable<string|BookletError> {
+  getBooklet(bookletName: string): Observable<string|BookletError> {
     const headers = new HttpHeaders({ 'Content-Type': 'text/xml' }).set('Accept', 'text/xml');
     const missingFileError: BookletError = { error: 'missing-file', species: null };
     const generalError: BookletError = { error: 'general', species: null };
@@ -39,7 +40,7 @@ export class BackendService extends WebsocketBackendService<TestSessionData[]> {
       );
   }
 
-  public getGroupData(groupName: string): Observable<GroupData> {
+  getGroupData(groupName: string): Observable<GroupData> {
     // TODO error-handling: interceptor should have interfered and moved to error-page ...
     // https://github.com/iqb-berlin/testcenter-frontend/issues/53
     return this.http
@@ -50,9 +51,7 @@ export class BackendService extends WebsocketBackendService<TestSessionData[]> {
       })));
   }
 
-  public command(keyword: string, args: string[], testIds: number[]): Subscription {
-    // TODO error-handling: interceptor should have interfered and moved to error-page ...
-    // https://github.com/iqb-berlin/testcenter-frontend/issues/53
+  command(keyword: string, args: string[], testIds: number[]): Observable<CommandResponse> {
     return this.http
       .put(
         `${this.serverUrl}monitor/command`,
@@ -63,25 +62,24 @@ export class BackendService extends WebsocketBackendService<TestSessionData[]> {
           testIds
         }
       )
-      .pipe(catchError(() => of(false)))
-      .subscribe();
+      .pipe(
+        map(() => ({ commandType: keyword, testIds }))
+      );
   }
 
-  unlock(group_name: string, testIds: number[]): Subscription {
-    // TODO interceptor should have interfered and moved to error-page ...
-    // https://github.com/iqb-berlin/testcenter-frontend/issues/53
+  unlock(groupName: string, testIds: number[]): Observable<CommandResponse> {
     return this.http
-      .post(`${this.serverUrl}monitor/group/${group_name}/tests/unlock`, { testIds })
-      .pipe(catchError(() => of(false)))
-      .subscribe();
+      .post(`${this.serverUrl}monitor/group/${groupName}/tests/unlock`, { testIds })
+      .pipe(
+        map(() => ({ commandType: 'unlock', testIds }))
+      );
   }
 
-  lock(group_name: string, testIds: number[]): Subscription {
-    // TODO interceptor should have interfered and moved to error-page ...
-    // https://github.com/iqb-berlin/testcenter-frontend/issues/53
+  lock(groupName: string, testIds: number[]): Observable<CommandResponse> {
     return this.http
-      .post(`${this.serverUrl}monitor/group/${group_name}/tests/lock`, { testIds })
-      .pipe(catchError(() => of(false)))
-      .subscribe();
+      .post(`${this.serverUrl}monitor/group/${groupName}/tests/lock`, { testIds })
+      .pipe(
+        map(() => ({ commandType: 'unlock', testIds }))
+      );
   }
 }
diff --git a/src/app/group-monitor/group-monitor.component.html b/src/app/group-monitor/group-monitor.component.html
index 5f953adca97b9627df3d6ca09ad015c5aeafdd34..1a1bc57239d6ae80a291131f6f88029a1472b8d2 100644
--- a/src/app/group-monitor/group-monitor.component.html
+++ b/src/app/group-monitor/group-monitor.component.html
@@ -168,9 +168,7 @@
       </div>
 
       <div class="toolbar-section">
-        <div *ngFor="let warning of warnings | keyvalue" class="alert-warning">
-          <mat-icon>warning</mat-icon>{{warning.value.text}}
-        </div>
+        <alert *ngFor="let m of messages" [text]="m.text" [level]="m.level"></alert>
       </div>
 
       <div class="toolbar-section toolbar-section-bottom">
diff --git a/src/app/group-monitor/group-monitor.component.ts b/src/app/group-monitor/group-monitor.component.ts
index 3fa019947aff8b49a46c03a7a61a09b35a3a4a2f..972379757b97e0b9150ed6a47c7d24ba246816dd 100644
--- a/src/app/group-monitor/group-monitor.component.ts
+++ b/src/app/group-monitor/group-monitor.component.ts
@@ -4,17 +4,17 @@ import {
 import { ActivatedRoute, Router } from '@angular/router';
 import { Sort } from '@angular/material/sort';
 import { MatSidenav } from '@angular/material/sidenav';
-import { Observable, Subscription } from 'rxjs';
-
+import { interval, Observable, Subscription } from 'rxjs';
 import { MatDialog } from '@angular/material/dialog';
-import { ConfirmDialogComponent, ConfirmDialogData } from 'iqb-components';
+import { ConfirmDialogComponent, ConfirmDialogData, CustomtextService } from 'iqb-components';
 import { MatSlideToggleChange } from '@angular/material/slide-toggle';
 import { MatCheckboxChange } from '@angular/material/checkbox';
+import { switchMap } from 'rxjs/operators';
 import { BackendService } from './backend.service';
 import {
   GroupData,
   TestViewDisplayOptions,
-  TestViewDisplayOptionKey, Selection, TestSession, TestSessionSetStats
+  TestViewDisplayOptionKey, Selection, TestSession, TestSessionSetStats, CommandResponse, UIMessage
 } from './group-monitor.interfaces';
 import { ConnectionStatus } from '../shared/websocket-backend.service';
 import { GroupMonitorService } from './group-monitor.service';
@@ -30,7 +30,8 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
     private route: ActivatedRoute,
     private bs: BackendService, // TODO move completely to service
     public gms: GroupMonitorService,
-    private router: Router
+    private router: Router,
+    private cts: CustomtextService
   ) {}
 
   ownGroup$: Observable<GroupData>;
@@ -54,7 +55,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
   isScrollable = false;
   isClosing = false;
 
-  warnings: { [key: string]: { text: string, timeout: number } } = {};
+  messages: UIMessage[] = [];
 
   private routingSubscription: Subscription = null;
 
@@ -74,6 +75,25 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
       this.onCheckedChange(stats);
     });
     this.connectionStatus$ = this.bs.connectionStatus$;
+    this.gms.commandResponses$.subscribe(commandResponse => {
+      this.messages.push(this.commandResponseToMessage(commandResponse));
+    });
+    this.gms.commandResponses$
+      .pipe(switchMap(() => interval(2000)))
+      .subscribe(() => this.messages.shift());
+  }
+
+  private commandResponseToMessage(commandResponse: CommandResponse): UIMessage {
+    if (!commandResponse.testIds.length) {
+      return {
+        level: 'warning',
+        text: `No Sessions affected by \`${commandResponse.commandType}\``
+      };
+    }
+    return {
+      level: 'info',
+      text: `Sent \`${commandResponse.commandType}\` to \`${commandResponse.testIds.length}\` sessions`
+    };
   }
 
   ngOnDestroy(): void {
@@ -83,6 +103,10 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
     this.gms.disconnect();
   }
 
+  ngAfterViewChecked(): void {
+    this.isScrollable = this.mainElem.nativeElement.clientHeight < this.mainElem.nativeElement.scrollHeight;
+  }
+
   private onSessionsUpdate(stats: TestSessionSetStats): void {
     this.displayOptions.highlightSpecies = (stats.differentBookletSpecies > 1);
 
@@ -110,10 +134,6 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
     this.displayOptions[option] = value;
   }
 
-  ngAfterViewChecked(): void {
-    this.isScrollable = this.mainElem.nativeElement.clientHeight < this.mainElem.nativeElement.scrollHeight;
-  }
-
   scrollDown(): void {
     this.mainElem.nativeElement.scrollTo(0, this.mainElem.nativeElement.scrollHeight);
   }
@@ -176,15 +196,18 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
   }
 
   unlockCommand(): void {
-    this.gms.testCommandUnlock().add(() => {
-      const plural = this.gms.sessions.length > 1;
-      // TODO zahl stimmt nicht
-      this.addWarning('reload-some-clients',
-        `${plural ? this.gms.sessions.length : 'Ein'} Test${plural ? 's' : ''} 
-        wurde${plural ? 'n' : ''} entsperrt. ${plural ? 'Die' : 'Der'} Teilnehmer 
-        ${plural ? 'müssen' : 'muss'} die Webseite aufrufen bzw. neuladen, 
-        damit ${plural ? 'die' : 'der'} Test${plural ? 's' : ''} wieder aufgenommen werden kann!`);
-    });
+    this.gms.testCommandUnlock();
+    //   .subscribe(commandResponse => {
+    //     if (commandResponse.error) {
+    //       const plural = this.gms.sessions.length > 1;
+    //       this.addWarning('reload-some-clients',
+    //         `${plural ? this.gms.sessions.length : 'Ein'} Test${plural ? 's' : ''}
+    //       wurde${plural ? 'n' : ''} entsperrt. ${plural ? 'Die' : 'Der'} Teilnehmer
+    //       ${plural ? 'müssen' : 'muss'} die Webseite aufrufen bzw. neuladen,
+    //       damit ${plural ? 'die' : 'der'} Test${plural ? 's' : ''} wieder aufgenommen werden
+    //       ${plural ? 'können' : 'kann'}!`);
+    //     }
+    //   });
   }
 
   toggleChecked(checked: boolean, session: TestSession): void {
@@ -201,16 +224,6 @@ export class GroupMonitorComponent implements OnInit, OnDestroy {
     return false;
   }
 
-  private addWarning(key, text): void {
-    if (typeof this.warnings[key] !== 'undefined') {
-      window.clearTimeout(this.warnings[key].timeout);
-    }
-    this.warnings[key] = {
-      text,
-      timeout: window.setTimeout(() => delete this.warnings[key], 30000)
-    };
-  }
-
   toggleAlwaysCheckAll(event: MatSlideToggleChange): void {
     if (this.gms.checkingOptions.disableAutoCheckAll && !event.checked) {
       this.gms.checkAll();
diff --git a/src/app/group-monitor/group-monitor.interfaces.ts b/src/app/group-monitor/group-monitor.interfaces.ts
index ba994fab80bd757fd460d130e791e76b86fcffb0..87d697b4b4d4b229606c428465a410572d52980d 100644
--- a/src/app/group-monitor/group-monitor.interfaces.ts
+++ b/src/app/group-monitor/group-monitor.interfaces.ts
@@ -152,3 +152,13 @@ export interface TestSessionSetStats {
   paused: number;
   locked: number;
 }
+
+export interface UIMessage {
+  level: 'error' | 'warning' | 'info' | 'success';
+  text: string;
+}
+
+export interface CommandResponse {
+  commandType: string;
+  testIds: number[];
+}
diff --git a/src/app/group-monitor/group-monitor.module.ts b/src/app/group-monitor/group-monitor.module.ts
index ffb4577261a70ad7bf9b7e397a86f5073db9df2d..00625b1fd19740a5f662387a029a2a6bc5e13e79 100644
--- a/src/app/group-monitor/group-monitor.module.ts
+++ b/src/app/group-monitor/group-monitor.module.ts
@@ -24,6 +24,7 @@ import { BookletService } from './booklet.service';
 import { TestViewComponent } from './test-view/test-view.component';
 import { TestSessionService } from './test-session.service';
 import { GroupMonitorService } from './group-monitor.service';
+import { AlertModule } from '../shared/alert/alert.module';
 
 @NgModule({
   declarations: [
@@ -48,7 +49,8 @@ import { GroupMonitorService } from './group-monitor.service';
     MatSidenavModule,
     MatCheckboxModule,
     MatSlideToggleModule,
-    IqbComponentsModule
+    IqbComponentsModule,
+    AlertModule
   ],
   providers: [
     BackendService,
diff --git a/src/app/group-monitor/group-monitor.service.ts b/src/app/group-monitor/group-monitor.service.ts
index d5dd4e46563189115ec30c73b17f1d1d88265c6d..19d25251768a43b67180136c099c726112cd85fe 100644
--- a/src/app/group-monitor/group-monitor.service.ts
+++ b/src/app/group-monitor/group-monitor.service.ts
@@ -12,13 +12,14 @@ import {
   Selection, CheckingOptions,
   TestSession,
   TestSessionFilter, TestSessionSetStats,
-  TestSessionsSuperStates
+  TestSessionsSuperStates, CommandResponse
 } from './group-monitor.interfaces';
 
 /**
  * func:
  * # checkAll
  * - stop / resume usw. ohne erlaubnis-check! sonst macht alwaysAll keinen Sinn
+ * --> customText und alert kombinieren!
  * - automatisch den nächsten wählen (?)
  * - problem beim markieren
  * tidy:
@@ -58,11 +59,16 @@ export class GroupMonitorService {
     return this._checkedStats$.asObservable();
   }
 
+  get commandResponses$(): Observable<CommandResponse> {
+    return this._commandResponses$.asObservable();
+  }
+
   private monitor$: Observable<TestSession[]>;
   private _sessions$: BehaviorSubject<TestSession[]>;
   private _checked: { [sessionTestSessionId: number]: TestSession } = {};
   private _checkedStats$: BehaviorSubject<TestSessionSetStats>;
   private _sessionsStats$: BehaviorSubject<TestSessionSetStats>;
+  private _commandResponses$: Subject<CommandResponse>;
 
   filterOptions: { label: string, filter: TestSessionFilter, selected: boolean }[] = [
     {
@@ -101,6 +107,7 @@ export class GroupMonitorService {
 
     this._checkedStats$ = new BehaviorSubject<TestSessionSetStats>(GroupMonitorService.getEmptyStats());
     this._sessionsStats$ = new BehaviorSubject<TestSessionSetStats>(GroupMonitorService.getEmptyStats());
+    this._commandResponses$ = new Subject<CommandResponse>();
 
     this.monitor$ = this.bs.observeSessionsMonitor()
       .pipe(
@@ -244,50 +251,74 @@ export class GroupMonitorService {
     const testIds = this.checked
       .filter(TestSessionService.isPaused)
       .map(session => session.data.testId);
-    this.bs.command('resume', [], testIds);
+    if (!testIds.length) {
+      this._commandResponses$.next({ commandType: 'resume', testIds });
+      return;
+    }
+    this.bs.command('resume', [], testIds).subscribe(
+      response => this._commandResponses$.next(response)
+    );
   }
 
   testCommandPause(): void {
     const testIds = this.checked
       .filter(session => !TestSessionService.isPaused(session))
       .map(session => session.data.testId);
-    this.bs.command('pause', [], testIds);
+    if (!testIds.length) {
+      this._commandResponses$.next({ commandType: 'pause', testIds });
+      return;
+    }
+    this.bs.command('pause', [], testIds).subscribe(
+      response => this._commandResponses$.next(response)
+    );
   }
 
   testCommandGoto(selection: Selection): void {
-    interface BookletToGotoMap {
+    const allTestIds: number[] = [];
+    const groupedByBooklet: {
       [bookletName: string]: {
-        sessionIds: number[],
+        testIds: number[],
         firstUnitId: string
       }
-    }
-
-    const groupedByBooklet: BookletToGotoMap = this.checked
-      .reduce((agg: BookletToGotoMap, session): BookletToGotoMap => {
-        if (!agg[session.data.bookletName] && isBooklet(session.booklet)) {
-          const firstUnit = BookletService.getFirstUnitOfBlock(selection.element.blockId, session.booklet);
-          if (firstUnit) {
-            agg[session.data.bookletName] = {
-              sessionIds: [],
-              firstUnitId: firstUnit.id
-            };
-          }
+    } = {};
+
+    this.checked.forEach(session => {
+      allTestIds.push(session.data.testId);
+      if (!groupedByBooklet[session.data.bookletName] && isBooklet(session.booklet)) {
+        const firstUnit = BookletService.getFirstUnitOfBlock(selection.element.blockId, session.booklet);
+        if (firstUnit) {
+          groupedByBooklet[session.data.bookletName] = {
+            testIds: [],
+            firstUnitId: firstUnit.id
+          };
         }
-        agg[session.data.bookletName].sessionIds.push(session.data.testId);
-        return agg;
-      }, {});
-
-    Object.keys(groupedByBooklet)
-      .forEach(booklet => {
-        this.bs.command('goto', ['id', groupedByBooklet[booklet].firstUnitId], groupedByBooklet[booklet].sessionIds);
+      }
+      groupedByBooklet[session.data.bookletName].testIds.push(session.data.testId);
+      return groupedByBooklet;
+    });
+
+    zip(
+      ...Object.keys(groupedByBooklet)
+        .map(key => this.bs.command('goto', ['id', groupedByBooklet[key].firstUnitId], groupedByBooklet[key].testIds))
+    ).subscribe(() => {
+      this._commandResponses$.next({
+        commandType: 'goto',
+        testIds: allTestIds
       });
+    });
   }
 
-  testCommandUnlock(): Subscription {
-    const sessionIds = this.checked
+  testCommandUnlock(): void {
+    const testIds = this.checked
       .filter(TestSessionService.isLocked)
       .map(session => session.data.testId);
-    return this.bs.unlock(this.groupName, sessionIds);
+    if (!testIds.length) {
+      this._commandResponses$.next({ commandType: 'unlock', testIds });
+      return;
+    }
+    this.bs.unlock(this.groupName, testIds).subscribe(
+      response => this._commandResponses$.next(response)
+    );
   }
 
   isChecked(session: TestSession): boolean {
@@ -404,7 +435,7 @@ export class GroupMonitorService {
       .map(session => session.data.testId);
 
     return this.bs.command('terminate', [], getUnlockedConnectedTestIds()) // kill running tests
-      .add(() => {
+      .subscribe(() => {
         setTimeout(() => this.bs.lock(this.groupName, getUnlockedTestIds()), 2000); // lock everything
       });
   }