Webhooks
Webhooks sind automatisierte Benachrichtigungen an definierte URLs, die in Echtzeit über Ereignisse informieren. Sie sind nützlich, da sie eine sofortige Reaktion auf spezifische Ereignisse ermöglichen, ohne dass eine kontinuierliche Abfrage erforderlich ist. Webhooks tragen dazu bei, die Systeme effizient und reaktionsschnell zu halten.
Ihre Webhooks werden sofort bei Eintritt eines Ereignisses aufgerufen. Können wir Ihre URL nicht erreichen oder antwortet sie mit einem Fehlerstatus, wiederholen wir den Versand nach einem festen Plan und prüfen über mehrere Tage hinweg automatisch, ob der Empfänger dauerhaft ausfällt. Details dazu finden Sie weiter unten unter Zustellung & Retries.
Zustellung & Retries
Wann wir einen Webhook wiederholen
Ein Webhook wird ausschließlich dann erneut zugestellt, wenn die Antwort Ihres Servers auf einen temporären Fehler hindeutet:
- Name
HTTP 429- Type
- Rate-Limit
- Description
Ihr Endpunkt signalisiert, dass er gerade zu viele Anfragen erhält.
- Name
HTTP 5xx- Type
- Server-Fehler
- Description
Konkret
500,502,503und504sowie die Cloudflare-Statuscodes520bis524(z.B.521„Web Server Is Down", das Cloudflare bei proxied Domains anstelle von502zurückgibt).
- Name
Verbindungsfehler- Type
- Netzwerk
- Description
Timeout, DNS-Auflösungsfehler, TLS-/Handshake-Fehler oder ein Verbindungsabbruch während des Requests.
Alle übrigen Antworten gelten als final und führen nicht zu einem erneuten Versand. Das gilt auch für 4xx-Codes wie 400, 401, 403 oder 404, da diese in der Regel auf einen Konfigurationsfehler bei der Empfängerseite hinweisen, der durch ein bloßes Wiederholen nicht behoben werden kann. Antworten im Bereich 2xx und 3xx werden als erfolgreich gewertet.
Request-Timeout
Jeder einzelne Zustellversuch hat ein Timeout von 5 Sekunden. Antwortet Ihr Server nicht in dieser Zeit, werten wir den Versuch als Fehler und planen einen Retry nach dem unten beschriebenen Plan ein.
Retry-Plan
Schlägt ein Versuch fehl, wird der nächste nach einem festen, ansteigenden Intervall geplant. Insgesamt unternehmen wir bis zu 14 Versuche, danach wird der Job verworfen. Die gesamte Zustellspanne beträgt damit ca. 3,5 Tage.
| Versuch | Wartezeit seit vorigem Versuch |
|---|---|
| 1 (initial) | sofort |
| 2 | 1 Minute |
| 3 | 5 Minuten |
| 4 | 30 Minuten |
| 5 | 60 Minuten |
| 6 | 2 Stunden |
| 7 bis 14 | je 10 Stunden |
Jeder Versuch wird separat signiert und trägt frische X-Timestamp-, X-Nonce- und X-Signature-Header, siehe unten unter Signatur prüfen.
Automatische Deaktivierung bei dauerhaften Fehlern
Zusätzlich zu den Einzel-Retries beobachten wir laufend die Tages-Fehlerquote pro Webhook. Ein Webhook wird als „problematisch" eingestuft, sobald an einem Tag
- die heutige Failure-Rate über 80 % liegt und
- mindestens 10 Fehler an dem Tag aufgetreten sind.
Ab diesem Zeitpunkt wird der Webhook über mehrere Tage hinweg beobachtet. Verbessert sich das Verhalten nicht, eskaliert das System schrittweise:
| Tag | Schwelle (rolling Failure-Rate) | Aktion |
|---|---|---|
| 1 | über 80 % | Warn-E-Mail Stufe 1 |
| 3 | über 85 % | Warn-E-Mail Stufe 2 |
| 5 | über 90 % | Warn-E-Mail Stufe 3 |
| 7 | über 95 % | Webhook wird automatisch deaktiviert, Eintrag im Audit-Log und Deaktivierungs-E-Mail |
Die Tageszähler werden ab dem Tag der Problemerkennung gemessen.
Nach einer Auto-Deaktivierung
Ein automatisch deaktivierter Webhook bleibt inaktiv, bis Sie ihn manuell im Dashboard wieder aktivieren. Nach der Reaktivierung startet die Statistik frisch.
Was zählt als „erfolgreich"?
Für die Failure-Rate-Auswertung gilt jede Antwort als „ok", die kein Retry-Auslöser ist. Konkret zählen 2xx, 3xx und auch 4xx-Antworten (mit Ausnahme von 429) als erfolgreich, weil sie aus Sicht des Versandsystems eine eindeutige, finale Rückmeldung darstellen.
Das bedeutet: ein dauerhaft mit 404 antwortender Endpunkt wird nicht automatisch deaktiviert, sondern muss von Ihnen manuell entfernt oder korrigiert werden.
Sicherheit
Quell IP prüfen
Alle Webhooks stammen von unserer IPv4 195.201.160.143 bzw. von der IPv6 2a01:4f8:13a:8e7::2. Sollte sich dies ändern, erfahren Sie alle weiteren Details dazu in unserem Changelog.
Signatur prüfen
Alle Webhooks an Ihren Server werden mit dem Signierschlüssel Ihres Accounts signiert. Anhand dieser Daten können Sie bei jedem empfangenen Webhook die Echtheit der Requests von uns validieren und eine Doppelung oder betrügerische Anfragen vermeiden. Überprüfen Sie dazu die Signatur wie auf der Seite Signierung beschrieben.
Folgende Header senden wir Ihnen in jedem Webhook:
- Name
X-Signature- Type
- string
- Description
Die von uns generierte Signatur
- Name
X-Timestamp- Type
- integer
- Description
Zeitstempel, zu dem die Signatur von uns erstellt wurde
- Name
X-Nonce- Type
- string
- Description
Zufällig generierter String mit 32 Zeichen
Jeder einzelne Zustellversuch wird separat signiert. Der erste Versand, jeder automatische Retry aus dem Retry-Plan und ein manueller erneuter Versand aus dem Webhook-Debugger tragen jeweils einen eigenen X-Timestamp, ein eigenes X-Nonce und eine dazu passende X-Signature. Ein Retry, der Stunden nach dem Ereignis eintrifft, trägt also weiterhin einen aktuellen Zeitstempel und ein Nonce, das vorher nie verwendet wurde.
Damit gelten das 30-Sekunden-Fenster für den Zeitstempel und die Einzigartigkeit des Nonce aus der Seite Signierung unverändert auch für Retries. Belassen Sie Ihr Prüffenster bei 30 Sekunden und weiten Sie es für Retries nicht auf.
Das Feld webhook_timestamp im Payload ist der Zeitpunkt des Ereignisses, nicht der Zeitpunkt des Zustellversuchs. Bei einem Retry weicht es bewusst vom Header X-Timestamp ab. Validieren Sie immer den Header-Wert, nie das Feld aus dem Payload.
Beispiele als JSON Payload
SMS
Eingehende SMS (sms_mo)
{
"data": {
"id": "681590",
"sender": "SMS",
"system": "491771783130",
"text": "Hello World",
"time": "1605878104",
"message_type": "SMS"
},
"webhook_event": "sms_mo",
"webhook_timestamp": "2020-12-02 11:55:44"
}
SMS-Statusänderung (dlr)
{
"data": {
"msg_id": "77149843739",
"status": "TRANSMITTED",
"timestamp": "1760972114.3369"
},
"webhook_event": "dlr",
"webhook_timestamp": "1760973114.1684"
}
Performance Tracking (tracking)
{
"webhook_event": "tracking",
"webhook_timestamp": "2022-07-27T07:38:18+02:00",
"data": {
"sms_id": "77182424125",
"sms_label": null,
"tracking_url": "https://svn.me/7oz",
"final_url": "https://www.google.com",
"type": "click",
"total_clicks": 5,
"total_views": 3
}
}
Voice
Sprachnachricht-Statusänderung (voice_status)
{
"data": {
"callerId": "49176123456789",
"duration": "4",
"id": "284195",
"pricePerMinute": 0.075,
"recipient": "4943160049851",
"status": "completed",
"timestamp": 1629786769
},
"webhook_event": "voice_status",
"webhook_timestamp": "2021-08-24T08:32:50+02:00"
}
Eingehender Anruf (voice_call)
{
"webhook_event": "voice_call",
"webhook_timestamp": "2024-08-02T07:28:59+02:00",
"data": {
"id": 0,
"caller": "4943160049851",
"time": 1722576539,
"system": "4915170517246"
}
}
DTMF-Signal (voice_dtmf)
{
"webhook_event": "voice_dtmf",
"webhook_timestamp": "2024-08-02T07:28:59+02:00",
"data": {
"id": 0,
"callerId": "4943160049851",
"recipient": "4943160049851",
"status": "completed",
"system": "4915170517246",
"timestamp": 1722576539,
"duration": 2.76,
"pricePerMinute": 0.045,
"dtmf_digit": 9,
"total_price": 0.045
}
}
RCS
Nachricht wurde zugestellt
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:01:07+01:00",
"data":
{
"msg_id": "77233699836",
"status": "DELIVERED",
"timestamp": "1709888466.254410",
"foreign_id": null,
"agent_id": "myfancyagent"
}
}
Nachricht wurde gelesen
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:01:09+01:00",
"data":
{
"msg_id": "77233699836",
"status": "READ",
"timestamp": "1709888468.065783",
"foreign_id": null,
"agent_id": "myfancyagent"
}
}
Statusbericht einer gesendeten Nachricht
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:00:18+01:00",
"data":
{
"sender": "4915153952979",
"status": "IS_TYPING",
"agent_id": "myfancyagent"
}
}
Es wurde eine Textnachricht gesendet
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T09:47:12+01:00",
"data": {
"id": 1871353,
"sender": "4915153952979",
"time": 1709870553,
"message_type": "RCS",
"content_type": "text",
"text": "Hallo",
"agent_id": "myfancyagent"
}
}
Es wurde eine vorgeschlagene Antwort ausgewählt
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T05:59:33+01:00",
"data": {
"id": 1870983,
"sender": "4915153952979",
"time": 1709870129,
"message_type": "RCS",
"content_type": "suggestion_response",
"suggestion_response": {
"postbackData": "suggestion_2",
"text": "Suggestion #2",
"type": "REPLY"
},
"agent_id": "myfancyagent"
}
}
Es wurde ein Bild gesendet
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T08:58:20+01:00",
"data": {
"id": 1871195,
"sender": "4915153952979",
"time": 1709870556,
"message_type": "RCS",
"content_type": "user_file",
"user_file": {
"thumbnail": {
"mimeType": "image/jpeg",
"fileSizeBytes": 10166,
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f36357145416a773452537737696e495a366a68454a42344639574937716e62704156765358664d486e42776730337334675a76522b6d4f574b6e32626632546842493d.jpeg"
},
"payload": {
"mimeType": "image/jpeg",
"fileSizeBytes": 611314,
"fileName": "IMG_20240308_050231_01.jpg",
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f76746e45515768746866717653444e6166336a513852375249614d753637656f414a6f4758624d5358417269306e767331493645372b47446654325a66764b3168453d.jpeg"
}
},
"agent_id": "myfancyagent"
}
}
Es wurde eine Datei gesendet
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T11:07:51+01:00",
"data": {
"id": 1871485,
"sender": "4915153952979",
"time": 1709892233,
"message_type": "RCS",
"content_type": "user_file",
"user_file": {
"payload": {
"mimeType": "application/pdf",
"fileSizeBytes": 18810,
"fileName": "sample (1).pdf",
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/617678715267303d/4f616c675256696b73524c6c75337a4b5a66336a51705570464e665a752f6d4e70464d37546e504b5458423730556a7337314e70514c3241436650304d616e4b3078493d.pdf"
}
},
"agent_id": "myfancyagent"
}
}
Es wurde ein Standort geteilt
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T11:07:51+01:00",
"data": {
"id": 1871485,
"sender": "4915153952979",
"time": 1709892233,
"message_type": "RCS",
"content_type": "location",
"location": {
"latitude": 54.3216562,
"longitude": 10.1350767
},
"agent_id": "myfancyagent"
}
}
Eingehende WhatsApp-Nachrichten werden mit dem Event wa_mo zugestellt. Das data-Objekt enthält immer dieselben Basisfelder, unabhängig davon, was der Kunde gesendet hat:
- Name
id- Type
- integer
- Description
Interne ID der eingehenden Nachricht
- Name
sender- Type
- string
- Description
Rufnummer des Absenders im internationalen Format ohne führendes Plus
- Name
time- Type
- integer
- Description
Unix-Zeitstempel des Empfangs
- Name
text- Type
- string
- Description
Nachrichteninhalt. Klartext bei Textnachrichten, ein JSON-kodierter String bei allen anderen Inhaltstypen
- Name
message_type- Type
- string
- Description
Immer
WA
- Name
system- Type
- string
- Description
Ihre WhatsApp-Business-Nummer, an die die Nachricht gesendet wurde
Setzen Sie am Webhook einen Event-Filter, wird dieser gegen system geprüft. So können Sie Nachrichten je WhatsApp-Business-Nummer verteilen.
Eingehende Textnachricht (wa_mo)
{
"webhook_event": "wa_mo",
"webhook_timestamp": "2026-08-21T14:50:12+02:00",
"data": {
"id": 4521987,
"sender": "353861234567",
"time": 1755780612,
"text": "Hello World",
"message_type": "WA",
"system": "4915170517246"
}
}
Andere Inhaltstypen als reiner Text
Medien, Standorte, Kontakte, Quick-Reply-Buttons und interaktive Antworten werden im selben Umschlag zugestellt. Das Feld text enthält dann einen JSON-kodierten String, dessen Schlüssel type angibt, was empfangen wurde:
type | Vom Kunden gesendet |
|---|---|
user_file | Bild, Video, Audio, Dokument oder Sticker |
location | Ein geteilter Standort |
contact | Eine geteilte Kontaktkarte |
button | Quick-Reply-Button einer Template-Nachricht |
interactive | Antwort auf eine interaktive Nachricht (Button oder Liste) |
Dekodieren Sie text mit Ihrem JSON-Parser, bevor Sie auf diese Strukturen zugreifen. Textnachrichten sind nicht JSON-kodiert. Prüfen Sie daher das erste Zeichen oder greifen Sie schlicht auf den Rohstring zurück, wenn das Dekodieren fehlschlägt.
Dekodierter Inhalt des Feldes text:
Medien (user_file)
{
"type": "user_file",
"user_file": [
{
"type": "image",
"mediaId": "1234567890123456",
"mimeType": "image/jpeg",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"fileName": "IMG_20260821_145012.jpg",
"fileUri": "https://static.seven.io/uploads/whatsapp/61513d3d/6176746a/4f36357145416a773452537737/IMG_20260821_145012.jpg",
"caption": "Schau dir das an"
}
]
}
type wird aus dem MIME-Typ abgeleitet und ist einer von image, video, audio, document oder sticker. Können wir die Datei nicht von Meta herunterladen, fehlt fileUri und das Objekt enthält stattdessen "downloadFailed": true. Nutzen Sie in diesem Fall mediaId, um die Datei selbst abzurufen.
Standort
{
"type": "location",
"location": {
"latitude": 54.3216562,
"longitude": 10.1350767,
"name": "seven communications GmbH",
"address": "Alte Weide 7, 24116 Kiel"
}
}
Kontakt
{
"type": "contact",
"contact": {
"formattedName": "John Doe",
"firstName": "John",
"lastName": "Doe",
"middleName": null,
"phones": [{"phone": "+353861234567", "type": "CELL"}],
"emails": [{"email": "john@example.com", "type": "WORK"}],
"addresses": [],
"company": "Example Ltd",
"birthday": "1985-04-12"
}
}
Quick-Reply-Button
{
"type": "button",
"button": {
"text": "Termin bestätigen",
"payload": "confirm_2026_08_21"
}
}
Interaktive Antwort
{
"type": "interactive",
"interactive": {
"item_id": "option_2",
"title": "Option #2",
"description": "Lieferung am Freitag"
}
}
Reaktionen (Emoji-Antworten auf eine Nachricht) werden nicht per wa_mo zugestellt. Sie werden gespeichert und in den Conversations angezeigt, es gibt dafür jedoch kein Webhook-Event und keine Einstellung zum Aktivieren. Dasselbe gilt für Statusänderungen Ihrer versendeten Nachrichten: Diese werden als dlr-Events zugestellt, nicht als wa_mo.