# Webhooks

Source: https://www.veeting.com/de/developer-documentation/web-hooks

## Übersicht

Mit Webhooks ruft Veeting Ihr System auf, sobald in einem Meeting etwas geschieht. So müssen Sie die API nicht laufend abfragen. Tritt ein Event ein, sendet die Plattform einen HTTP-Aufruf an eine URL, die Sie konfiguriert haben. Im JSON-Body steht das betroffene Objekt.

Sie konfigurieren Webhooks an zwei Stellen. Beide lösen beim selben Event aus:

- **Auf Ebene der White-Label-Instanz**, im Abschnitt «Web Hooks» der Systemkonfiguration. Löst für jedes Konto der Instanz aus.
- **Auf Kontoebene**, pro Konto. Löst nur für dieses Konto aus.

Konfigurieren Sie beide, ruft die Plattform Ihren Endpunkt beim selben Event zweimal auf, einmal pro Ebene.

## Regeln für jeden Webhook

1. **Wir versuchen die Zustellung genau einmal.** Es gibt keine Wiederholung, kein Backoff und keine Dead-Letter-Queue. Ist Ihr Endpunkt nicht erreichbar oder antwortet er zu spät oder mit einem Fehlerstatus, loggen wir das Event auf unserer Seite. Auf Ihrer Seite ist es verloren. Richten Sie sich darauf ein: rasch bestätigen, die Arbeit intern in eine Queue legen und bei Bedarf das Meeting mit [`GET /meeting/{id}`](/de/developer-documentation/api-usage) zurücklesen, statt darauf zu vertrauen, dass jedes Event angekommen ist.

2. **Antworten Sie schnell.** Für die Zustellung ist kein Timeout konfiguriert. Ein langsamer Endpunkt hält deshalb eine Verbindung offen, statt zügig zu scheitern. Geben Sie ein `2xx` zurück, sobald Sie die Payload angenommen haben, und erledigen Sie die Arbeit danach.

3. **Der Body ist das Objekt selbst, kein Wrapper.** Anders als bei der REST API steckt eine Webhook-Payload nicht in `responseCode` und `data`. Zwei Events weichen ab: `onMeetingSummaryCreated` kommt in `responseCode` und `data` verpackt an, und `onMeetingRecordingCreated` kommt als Objekt an, in dem das Meeting steckt, statt als Meeting selbst. Beide sind unten beschrieben.

4. **Prüfen Sie die Form der Payload, bevor Sie sie lesen.** Manche Events senden ein einzelnes Objekt, andere ein Array. Welche das sind, steht unten.

## Konfiguration

Sie konfigurieren jeden Webhook einzeln, mit vier Einstellungen:

| Einstellung | Bedeutung                                                                          |
| ----------- | ---------------------------------------------------------------------------------- |
| enabled     | Ob dieser Webhook überhaupt auslöst                                                |
| url         | Der Endpunkt, den wir aufrufen                                                     |
| method      | `POST`, `PUT` oder `DELETE`. Leere oder unbekannte Werte fallen auf `POST` zurück. |
| apiKey      | Optional. Siehe unten.                                                             |

### Ihren Endpunkt schützen

Setzen Sie `apiKey`, sendet die Plattform ihn bei jedem Aufruf als `X-API-KEY`-Header. Ihr Endpunkt weist damit alle Aufrufe ohne diesen Header ab. Lassen Sie das Feld leer, sendet die Plattform gar keinen solchen Header.

Mehr Authentifizierung hat ein Webhook-Aufruf nicht. Einen Endpunkt ohne konfigurierten API-Key ruft jeder auf, der seine URL kennt.

`content-type: application/json` sendet die Plattform immer mit.

## Die Events

Es gibt 14 Events.

| Event                               | Löst aus, wenn                                                                  | Payload                   |
| ----------------------------------- | ------------------------------------------------------------------------------- | ------------------------- |
| `onMeetingScheduled`                | Ein Meeting erstellt wird                                                       | Meeting                   |
| `onMeetingUpdated`                  | Ein Meeting geändert wird                                                       | Meeting                   |
| `onMeetingClosed`                   | Ein Meeting geschlossen wird, automatisch oder manuell                          | Meeting                   |
| `onMeetingDeleted`                  | Meetings gelöscht werden                                                        | **Array** von Meeting     |
| `onMeetingCancelled`                | Ein einzelner Termin einer Serie abgesagt wird                                  | `{ meeting, day }`        |
| `onMeetingJoined`                   | Jemand einem Meeting beitritt                                                   | MeetingParticipantsStatus |
| `onMeetingLeft`                     | Jemand ein Meeting verlässt                                                     | MeetingParticipantsStatus |
| `onMeetingSummaryCreated`           | Eine Summary erstellt wird, üblicherweise rund fünf Minuten nach dem Schliessen | MeetingSummary, verpackt  |
| `onMeetingRecordingCreated`         | Ein aufgezeichnetes Meeting zusammengefügt wurde                                | Aufzeichnung              |
| `onMeetingsReminder15MinutesBefore` | 15 Minuten vor Meeting-Beginn                                                   | **Array** von Meeting     |
| `onMeetingsReminder30MinutesBefore` | 30 Minuten vor Meeting-Beginn                                                   | **Array** von Meeting     |
| `onMeetingsReminder60MinutesBefore` | 60 Minuten vor Meeting-Beginn                                                   | **Array** von Meeting     |
| `onMeetingsReminder24HoursBefore`   | 24 Stunden vor Meeting-Beginn                                                   | **Array** von Meeting     |
| `onMeetingsReminder48HoursBefore`   | 48 Stunden vor Meeting-Beginn                                                   | **Array** von Meeting     |

Zwei Dinge zu den Remindern übersieht man leicht:

- Sie stellen ein **Array** zu, denn ein Reminder-Lauf umfasst alle Meetings, die zu diesem Zeitpunkt anstehen. Auch ein einzelnes Meeting kommt als Array mit einem Eintrag an.
- Der Umfang hängt von der Ebene ab. Ein Reminder auf Ebene der White-Label-Instanz erhält jedes anstehende Meeting der Instanz, ein Reminder auf Kontoebene nur die Meetings dieses Kontos. Derselbe Lauf sendet also unterschiedliche Arrays an unterschiedliche Endpunkte.

## Payloads

### Meeting

Diese Payload senden `onMeetingScheduled`, `onMeetingUpdated` und `onMeetingClosed`. Es ist dasselbe Objekt, das die REST API bei `POST /meeting` zurückgibt, hier gekürzt:

```json
{
  "id": "5e945c36b863b82cefdddf54",
  "meetingId": "9974-7653-8886-0485",
  "topic": "My Meeting Topic",
  "startTime": "2026-09-07T09:00:00.000Z",
  "endTime": "2026-09-07T10:00:00.000Z",
  "duration": 60,
  "type": "standard",
  "roomId": "5e9459c5b863b82cefdddf50",
  "isRecurring": false,
  "isRecorded": false,
  "isDialin": false,
  "invitedParticipants": [],
  "agenda": "",
  "whitelabelId": "5c737902b377b0f7fbf81fce",
  "accountId": "5e9459c5b863b82cefdddf4f",
  "addedByUserId": "5e9459c5b863b82cefdddf4e",
  "addedByUserEmail": "test-user@example.com",
  "addedByUserName": "Test User",
  "timezone": "Europe/Zurich",
  "isActive": false,
  "isOpen": false,
  "isClosed": true
}
```

Die beiden IDs verhalten sich genau wie in der [REST API](/de/developer-documentation/api-usage): Verwenden Sie `id`, wenn Sie zurück in die API rufen, und `meetingId`, wenn Sie einen Link bauen oder das Meeting jemandem anzeigen.

### Array von Meeting

Diese Payload senden `onMeetingDeleted` und alle fünf Reminder. Es ist dasselbe Objekt wie oben, in einem Array:

```json
[
  { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic" }
]
```

Bei `onMeetingDeleted` kann jedes Meeting zusätzlich ein Feld `cancellationMessage` mitführen. Darin steht der Text, den der Organisator beim Löschen angegeben hat.

### Abgesagter Termin

Diese Payload sendet `onMeetingCancelled`, wenn jemand einen einzelnen Termin einer Serie absagt und nicht die ganze Serie. Das Meeting steckt verschachtelt im Body, und `day` bezeichnet den Termin:

```json
{
  "meeting": { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "isRecurring": true },
  "day": "2026-09-14"
}
```

### MeetingParticipantsStatus

Diese Payload senden `onMeetingJoined` und `onMeetingLeft`:

```json
{
  "meetingId": "5df78199c015b37195230596",
  "meetingToken": "0000-0000-0000-0000",
  "isNamedRoom": false,
  "participant": {
    "name": "Joe Doe",
    "email": "joe@example.com",
    "meetingParticipantId": "4edaf0d2-cb6c-42f1-a266-4ae7d971f2e6",
    "isModerator": false
  },
  "totalNumberOfGuests": 3,
  "totalNumberOfModerators": 1,
  "whitelabelId": "5c737902b377b0f7fbf81fce",
  "accountId": "5e9459c5b863b82cefdddf4f"
}
```

> **Hinweis:** Hier enthält `meetingId` die 24-stellige `id` des Meetings, und `meetingToken` die Nummer mit den Bindestrichen. Das ist genau umgekehrt zur Benennung überall sonst. Lesen Sie hier also besonders genau.

### MeetingSummary

Diese Payload sendet `onMeetingSummaryCreated`. Es ist die einzige, die in `responseCode` und `data` verpackt ankommt:

```json
{
  "responseCode": 0,
  "data": {
    "id": "5f7d49eb62e8f9a43b23f986",
    "meetingId": "5f7d49e662e8f9a43b23f983",
    "meetingType": "boardroom",
    "agenda": "",
    "minutes": "",
    "participants": [
      {
        "name": "Participant 1",
        "durations": [
          { "action": "joined", "timestamp": 1602046445811 },
          { "action": "left", "timestamp": 1602046670808 }
        ],
        "info": {
          "browserName": "Chrome",
          "browserVersion": "82.0.4062.0",
          "osName": "macOS",
          "osVersion": "10.15.6",
          "platformType": "desktop",
          "platformVendor": "Apple"
        }
      }
    ],
    "documents": [],
    "pdf": null,
    "accountId": "5c73790ab377b0f7fbf81fde",
    "isNamedRoom": false,
    "isRecordingPrepared": false,
    "sfuHostname": "ch-01-sfu-02.wlvmr.net",
    "meetingQuestions": [],
    "meetingConversationDuration": 25,
    "meetingConversationStartToEndDuration": 25,
    "meetingParticipantsStartToEndDuration": 35,
    "meetingStartToEndDuration": 60
  }
}
```

Die vier Dauer-Felder messen Unterschiedliches. Alle Werte sind Sekunden:

| Feld                                  | Was es misst                                                                    |
| ------------------------------------- | ------------------------------------------------------------------------------- |
| meetingConversationDuration           | Wie lange mindestens zwei Personen gleichzeitig im Raum waren                   |
| meetingConversationStartToEndDuration | Vom ersten Moment, in dem zwei Personen zusammen im Raum waren, bis zum letzten |
| meetingParticipantsStartToEndDuration | Vom Beitritt der ersten Person bis zum Verlassen der letzten                    |
| meetingStartToEndDuration             | Die geplante Dauer, einschliesslich Verlängerung                                |

### Aufzeichnung

Diese Payload sendet `onMeetingRecordingCreated`. Anders als die übrigen ist sie ein Wrapper: Meeting und Summary stecken darin, statt selbst der Body zu sein.

```json
{
  "meeting": { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic" },
  "summary": { "id": "5f48a701ee0c388a879bffa7" },
  "mergedRecordingUrl": "https://<recording-host>/prepared/<meeting>/<token>.webm",
  "recordingUrls": [
    "video-file-1.webm",
    "video-file-2.webm",
    "video-file-3.webm"
  ],
  "emailNotificationRecipients": ["organizer@example.com"]
}
```

| Eigenschaft                 | Bedeutung                                                                             |
| --------------------------- | ------------------------------------------------------------------------------------- |
| meeting                     | Das vollständige Meeting-Objekt, wie oben beschrieben                                 |
| summary                     | Die Summary, zu der diese Aufzeichnung gehört                                         |
| mergedRecordingUrl          | Die einzelne, zusammengefügte Aufzeichnung                                            |
| recordingUrls               | Die einzelnen Teile, aus denen sie entstand, so wie der Aufzeichnungs-Host sie meldet |
| emailNotificationRecipients | Wen die Plattform gleich über diese Aufzeichnung informiert                           |

> **Hinweis:** Lesen Sie die IDs des Meetings aus `meeting.id` und `meeting.meetingId`. Auf oberster Ebene dieser Payload gibt es kein `meetingId`.

## Häufige Fehler

- Darauf bauen, dass wir eine fehlgeschlagene Zustellung wiederholen. Wir tun es nicht.
- Die Payload eines Reminders als einzelnes Meeting behandeln. Alle fünf Reminder senden ein Array.
- `onMeetingSummaryCreated` als unverpacktes Objekt lesen. Es ist die einzige verpackte Payload.
- `meetingId` in der Join- und Leave-Payload als Nummer mit Bindestrichen lesen. Dort ist es die 24-stellige `id`, und `meetingToken` ist die mit den Bindestrichen.
- Denselben Endpunkt auf Instanz- und auf Kontoebene konfigurieren und den doppelten Aufruf dann für einen Fehler halten.
- `apiKey` bei einem Endpunkt leer lassen, der Daten verändert.
- Erst die langsame Arbeit erledigen und dann antworten. Niemand bricht den Aufruf für Sie ab.

---

## Die übrige Dokumentation

- [Custom Tools – eigene Widgets im Meetingraum](https://www.veeting.com/de/developer-documentation/custom-tools)
- [Externer Autorisierungsdienst](https://www.veeting.com/de/developer-documentation/external-meeting-authorization-service)
- [iFrame und Web Komponenten](https://www.veeting.com/de/developer-documentation/iframe-and-web-components)
- [JavaScript APIs](https://www.veeting.com/de/developer-documentation/javascript-apis)
- [JavaScript und Typescript APIs](https://www.veeting.com/de/veeting-blocks/apis)
- [Komponenten](https://www.veeting.com/de/veeting-blocks/components)
- [Kontrolle über die Videodarstellung](https://www.veeting.com/de/developer-documentation/video-display-calculator)
- [Parameter in der URL](https://www.veeting.com/de/developer-documentation/query-parameters)
- [Veeting Blocks - Übersicht](https://www.veeting.com/de/veeting-blocks/introduction)
- [Veeting Rooms REST APIs](https://www.veeting.com/de/developer-documentation/api-usage)

Alles in einer Datei: https://www.veeting.com/llms-full.txt
