Webhook-Grundlagen
Die WhatsApp Cloud API liefert Ereignisse über ein push-basiertes Webhook-System an Ihre Anwendung. Anstatt einen Endpunkt auf neue Ereignisse abzufragen, senden Metas Server eine JSON-Payload per POST an eine von Ihnen registrierte URL — Ihren Webhook-Endpunkt. Ihr Server verarbeitet die Payload und gibt HTTP 200 zurück, um den Empfang zu bestätigen.
Zwei HTTP-Methoden operieren auf derselben URL:
- GET — einmaliger Verifizierungs-Challenge während der Registrierung. Ihr Endpunkt muss den Query-Parameter
hub.challengeals Klartext zurückgeben. - POST — Live-Ereignisbenachrichtigungen. Jede eingehende Nachricht, Statusaktualisierung und Konto-Ereignis kommt hier an.
Die wichtigsten Einschränkungen, die Sie verinnerlichen müssen, bevor Sie Code schreiben:
- Antworten Sie innerhalb von 20 Sekunden — alles Langsamere löst einen Wiederholungsversuch aus. Bestätigen Sie sofort, verarbeiten Sie asynchron.
- At-Least-Once-Zustellung — dasselbe Ereignis kann mehr als einmal eintreffen. Ihr Handler muss idempotent sein.
- Keine Garantie für Reihenfolge — Ereignisse können außer der Reihe eintreffen. Niemals chronologische Reihenfolge annehmen.
- 3 MB Payload-Größenlimit — einzelne Webhook-Payloads überschreiten nicht 3 MB. Mediendateien sind nicht inline enthalten.
- HTTPS erforderlich — Meta lehnt reine HTTP-Endpunkte ab. Gültiges SSL-Zertifikat erforderlich.
Vollständige Webhook-Eigenschaften-Referenz
| Eigenschaft | Wert / Spezifikation | Hinweise |
|---|---|---|
| Protokoll | Nur HTTPS | Gültiges SSL-Zertifikat erforderlich. HTTP wird vollständig abgelehnt. |
| Richtung | Anbieter → Verbraucher (Push) | Meta pusht zu Ihnen. Kein Polling erforderlich. |
| Verifizierungsmethode | GET + hub.verify_token | Einmalig bei Registrierung. hub.challenge als Klartext zurückgeben. |
| Authentifizierung bei Ereignissen | X-Hub-Signature-256 HMAC-SHA256 | Mit App Secret signiert. Jeden POST verifizieren. |
| HTTP-Methode für Ereignisse | POST | Immer POST. Niemals GET für Live-Ereignisse. |
| Datenformat | JSON | Content-Type: application/json |
| Antwort-Timeout | 20 Sekunden | Überschreitung löst Wiederholung aus. Sofort 200 zurückgeben. |
| Erfolgskriterium | HTTP 200 | Jeder nicht-200-Status löst Wiederholungsmechanismus aus. |
| Payload-Größenlimit | 3 MB | Medien nicht inline — referenziert per ID. |
| Wiederholungsdauer | Bis zu 7 Tage | Exponentielles Backoff. Vollständiger Zeitplan unten. |
| Zustellgarantie | At-Least-Once | Duplikate möglich. Handler idempotent gestalten. |
| Reihenfolgegarantie | Keine | Ereignisse können außer chronologischer Reihenfolge eintreffen. |
| Webhook-Ebenen | Telefonnummer + WABA | Telefonnummer hat Vorrang vor WABA-Fallback. |
| Manuelles Replay | Nicht unterstützt | Kein natives Replay. Ereignisse nach 7 Tagen verloren. |
| Signatur-Algorithmus | HMAC-SHA256 | Schlüssel = App Secret. Eingabe = rohe Body-Bytes. |
| Signatur-Header | X-Hub-Signature-256 | Format: sha256=<hex> |
| Gleichzeitige Zustellungen | Mehrere pro Sekunde | High-Volume-Nummern erhalten viele Ereignisse/Sekunde. |
Alle Webhook-Abonnement-Felder
Sie abonnieren einzelne Felder im Meta Developer Dashboard (WhatsApp → Configuration → Webhooks → Manage). Das Abonnieren von messages ist für die meisten Integrationen obligatorisch. Jedes Feld repräsentiert eine Kategorie von Ereignissen — abonnieren Sie nur, was Sie benötigen, um das Payload-Volumen zu reduzieren.
| Feld | Abdeckung | Priorität |
|---|---|---|
| messages | Alle eingehenden Kundennachrichten + alle ausgehenden Statusaktualisierungen (sent / delivered / read / failed). Das primäre Feld für jede Messaging-Integration. | Immer abonnieren |
| account_update | Richtlinienverstöße (ACCOUNT_VIOLATION) und aktive Einschränkungen (ACCOUNT_RESTRICTION) bei Ihren Telefonnummern. Essenziell für Produktions-Monitoring. |
Empfohlen |
| message_template_status_update | Template-Genehmigung, -Ablehnung, -Pausierung und -Deaktivierung. Wird ausgelöst, wenn Meta den Status eines von Ihnen eingereichten Templates ändert. | Empfohlen |
| phone_number_quality_update | Änderungen der Qualitätsbewertung (GREEN / YELLOW / RED) für Ihre Telefonnummern. Qualität beeinflusst Ihr Messaging-Tier. Abonnieren, um Verschlechterung früh zu erkennen. | Empfohlen |
| phone_number_name_update | Ereignisse zur Genehmigungs- oder Ablehnung des Anzeigenamens. Wird ausgelöst, wenn Meta einen von Ihnen für Ihre WhatsApp Business-Nummer eingereichten Namen prüft. | Situativ |
| business_capability_update | Änderungen des Messaging-Limit-Tiers — wenn Meta Ihr tägliches Konversations-Tier hoch- oder herabstuft (1K / 10K / 100K / Unlimited). | Situativ |
| flows | WhatsApp Flows-Interaktionen — Datenübermittlungs-Ereignisse, wenn ein Nutzer einen mit Ihrer Nummer verknüpften Flow abschließt oder interagiert. | Bei Verwendung von Flows |
| security | Two-Step-Verification-PIN-Ereignisse. Wird ausgelöst, wenn die PIN einer Telefonnummer geändert oder deaktiviert wird. | Optional |
| message_template_components_update | Wird ausgelöst, wenn Meta die Komponenten eines genehmigten Templates ändert (selten — Meta kann Templates zur Richtlinienkonformität anpassen). | Optional |
| account_alerts | Abrechnungswarnungen, Kapazitätswarnungen und andere Konto-Ebenen-Betriebshinweise von Meta. | Optional |
Ereignisse pro Abonnement-Feld
Feld messages — eingehende Nachrichtentypen
Das Feld messages liefert zwei Kategorien: eingehende Nachrichten von Kunden (identifiziert durch ein messages-Array im value-Objekt) und ausgehende Zustell-Statusaktualisierungen (identifiziert durch ein statuses-Array). Prüfen Sie, welches Array vorhanden ist, bevor Sie verarbeiten.
| msg.type | Content-Position | Hinweise |
|---|---|---|
| text | msg.text.body | Klartext-Nachrichten-Body. Kann URLs enthalten. |
| image | msg.image.id, .mime_type, .caption | ID → URL auflösen → herunterladen. Caption optional. |
| audio | msg.audio.id, .voice | voice: true wenn in-App aufgenommen. Immer sofort herunterladen. |
| video | msg.video.id, .caption | Caption optional. |
| document | msg.document.id, .filename, .mime_type | Filename enthalten — speichern. |
| sticker | msg.sticker.id, .animated | Animated-Flag unterscheidet WebP von animiertem Sticker. |
| location | msg.location.latitude, .longitude, .name, .address | Name und Adresse sind optional. |
| contacts | msg.contacts[n].name, .phones | Array — Kunde kann mehrere Kontakte teilen. |
| reaction | msg.reaction.emoji, .message_id | Referenziert die Message-ID, auf die der Kunde reagiert hat. |
| interactive | msg.interactive.type, dann .button_reply oder .list_reply | type prüfen, bevor Sub-Objekt gelesen wird. |
| order | msg.order.catalog_id, .product_items | WhatsApp Commerce — Produktbestellung aus Katalog. |
| system | msg.system.body, .type | System-Ereignisse: Kunde hat Nummer geändert, etc. |
| button | msg.button.text, .payload | Quick-Reply-Button-Klick von einer Template-Nachricht. |
| referral | msg.referral.source_url, .source_type, .source_id | Click-to-WhatsApp-Ad-Referral-Daten neben der Nachricht. |
Feld messages — ausgehende Statusaktualisierungen
| Statuswert | Bedeutung | Zusätzliche Felder |
|---|---|---|
| sent | Nachricht von Meta akzeptiert und an WhatsApp weitergeleitet. Noch nicht am Gerät zugestellt. | timestamp, recipient_id, conversation, pricing |
| delivered | Nachricht hat das Gerät des Empfängers erreicht (doppeltes graues Häkchen). | timestamp, recipient_id, conversation, pricing |
| read | Empfänger hat den Chat geöffnet (doppeltes blaues Häkchen). Wird nur ausgelöst, wenn Lesebestätigungen aktiviert sind. | timestamp, recipient_id |
| failed | Zustellung dauerhaft fehlgeschlagen. status.errors[0].code auf spezifischen Fehler prüfen. | errors-Array mit code und title |
Exakter Wiederholungszeitplan mit Backoff-Zeiten
Meta wiederholt fehlgeschlagene Webhook-Zustellungen bis zu 7 Tage lang mit exponentiellem Backoff. "Fehlgeschlagen" bedeutet, dass Ihr Endpunkt einen nicht-200-Status zurückgegeben hat, ein Timeout hatte (länger als 20 Sekunden brauchte) oder nicht erreichbar war. Nach 7 Tagen wird das Ereignis endgültig verworfen — es gibt keinen nativen Wiederherstellungspfad.
HTTP-Antwortcode-Verhalten
Was Sie an Meta zurückgeben, bestimmt, ob das Ereignis als zugestellt gilt, wiederholt wird oder einen Alarm auslöst. Der Entscheidungsbaum ist einfacher, als die meisten Entwickler erwarten — alles ist binär: 200 bedeutet Erfolg, alles andere bedeutet Wiederholung.
Vollständige HMAC-SHA256-Signatur-Spezifikation
Jede POST-Anfrage von Meta enthält eine kryptografische Signatur, mit der Sie verifizieren können, dass die Anfrage tatsächlich von Meta stammt und während der Übertragung nicht manipuliert wurde. Das Überspringen dieser Prüfung bedeutet, dass jeder Angreifer, der Ihre Webhook-URL entdeckt, willkürliche Daten an Ihre Anwendung füttern kann.
Header-Format
Verifizierung in Node.js
Verifizierung in Python
JSON.stringify(JSON.parse(body)) erzeugt andere Bytes als das Original — Leerzeichen, Schlüsselreihenfolge und Zahlenpräzision können sich alle ändern. Berechnen Sie HMAC immer auf den exakten Bytes, die in der HTTP-Anfrage angekommen sind, vor jeglicher Transformation.WABA-Ebene vs. Telefonnummer-Ebene Webhooks
Die WhatsApp Cloud API unterstützt zwei Webhook-Konfigurationsebenen, die in einer spezifischen Prioritätsreihenfolge interagieren. Das Verständnis hierfür verhindert stillen Ereignisverlust beim Verwalten mehrerer Telefonnummern.
| Aspekt | Telefonnummer-Webhook | WABA-Webhook |
|---|---|---|
| Konfigurationsort | Meta Developer Dashboard → WhatsApp → Configuration | Meta Business Settings → WhatsApp Accounts → [WABA] → Webhook |
| Geltungsbereich | Eine spezifische Telefonnummer | Alle Telefonnummern im WABA |
| Priorität | Höher — hat Vorrang | Niedriger — nur Fallback |
| Fallback-Verhalten | Wenn gesetzt, erhält WABA-Webhook keine Ereignisse für diese Nummer | Erhält Ereignisse für Nummern ohne Telefonnummer-Webhook |
| Am besten für | Einzelnummern-Integrationen, pro-Nummer-Routing-Logik | Mehrnummern-Operationen, Agentur verwaltet viele Kundenummern |
| Ereignis-Routing | Ereignisse gehen nur an diese URL | Ereignisse für alle nicht konfigurierten Nummern kommen hier an |
| Abonnement-Felder | Separat konfiguriert | Separat konfiguriert |
metadata.phone_number_id dispatcht. Fügen Sie einen Telefonnummer-Ebenen-Webhook nur für Nummern hinzu, die unterschiedliches Routing benötigen. Dies ist sauberer als separate Webhooks pro Kundenummer zu verwalten, und SocialHook unterstützt mehrere Nummern pro Konto unter einem einzigen normalisierten Ereignis-Stream.At-Least-Once-Zustellung — Implikationen und Deduplizierung
Metas Webhook-System garantiert, dass ein gegebenes Ereignis mindestens einmal zugestellt wird. Es garantiert nicht genau einmal. Das Duplikat-Szenario: Ihr Server verarbeitet ein Ereignis, gibt 200 zurück, aber die Bestätigung geht auf dem Weg zu Metas System verloren. Meta wiederholt. Ihr Handler verarbeitet dasselbe Ereignis erneut.
Für einen einfachen Echo-Bot ist dies belanglos. Für einen AI-Agenten, der eine CRM-Aktion, eine Zahlung oder eine ausgehende Nachricht auslöst — verursachen Duplikate echte Probleme. Ihr Handler muss idempotent sein.
Deduplizierungs-Muster
Der Wert msg.id (die Zeichenkette wamid.HBgL...) ist eindeutig pro Nachricht. Status-Updates verwenden die ausgehende Nachrichten-ID. Reaktionen und andere Ereignistypen haben ihre eigenen eindeutigen IDs. Verwenden Sie immer die ereignisspezifische ID — niemals einen abgeleiteten Wert — als Ihren Deduplizierungsschlüssel.
Payload-Schemata für Schlüssel-Ereignistypen
Top-Level-Envelope (alle Ereignisse)
Eingehende Textnachricht (value-Objekt)
Ausgehende Nachrichten-Statusaktualisierung (value-Objekt)
Sicherheits-Checkliste
X-Hub-Signature-256-Header fehlt, fehlerhaft ist oder nicht übereinstimmt. Ohne dies akzeptiert Ihr Endpunkt gefälschte Ereignisse von jedem.crypto.timingSafeEqual() in Node.js, hmac.compare_digest() in Python. Einfache String-Gleichheit (=== / ==) leckt Timing-Informationen, die Angreifer ausnutzen können, um Signaturen Zeichen für Zeichen zu fälschen.express.raw()-Middleware auf der Webhook-Route. In FastAPI: await request.body() vor jeglicher JSON-Deserialisierung.process.env.WHATSAPP_APP_SECRET (Node.js) oder os.environ["WHATSAPP_APP_SECRET"] (Python) speichern. Sofort rotieren, wenn es leakt.msg.id-Werte in Redis mit 24h+ TTL speichern. Vor Verarbeitung prüfen. Duplikate still überspringen — niemals darauf mit Fehler reagieren.crypto.randomBytes(32).toString('hex').Häufige Fehler und Lösungen
| Fehler / Symptom | Ursache | Lösung |
|---|---|---|
| Verifizierung schlägt bei Registrierung fehl | JSON statt Klartext zurückgeben, falsches Verify-Token oder hub.challenge in ein JSON-Objekt eingewickelt zurückgeben |
hub.challenge als Klartext mit Content-Type: text/plain zurückgeben. Exakte Zeichenkette — kein JSON-Wrapping. |
| HMAC schlägt immer fehl | HMAC nach JSON-Parsing berechnen, Access Token statt App Secret verwenden, Body doppelt encodieren | express.raw() in Express verwenden. await request.body() in FastAPI vor dem Parsen aufrufen. Schlüssel = App Secret aus App Settings → Basic. |
| Ereignisse kommen überhaupt nicht an | messages-Feld nicht abonniert oder Webhook nicht gespeichert/verifiziert |
Dashboard → WhatsApp → Configuration → Webhooks → Manage → messages-Feld aktivieren. Bestätigen, dass Webhook als verifiziert angezeigt wird. |
| Dasselbe Ereignis mehrfach empfangen | At-Least-Once-Zustellung — erwartetes Verhalten, kein Bug | Deduplizierung implementieren mit msg.id als Redis-Schlüssel mit 24h TTL. Vor Verarbeitung prüfen. |
| Ereignisse kommen außer Reihenfolge an | Keine Reihenfolge-Garantie — by design | timestamp-Feld verwenden, um beim Aufbau von Konversations-Threads zu sortieren. Niemals sequenzielle Zustellreihenfolge annehmen. |
| Meta-Wiederholungen kommen ständig | Handler gibt nicht-200 zurück, Timeout (>20s) oder Absturz | Sofort 200 zurückgeben. gesamte Verarbeitung in asynchrone Queue verschieben. Handler in try-catch wickeln. Server-Stabilität überwachen. |
| Media-Download gibt 401 zurück | Falsches Token verwendet oder Token abgelaufen | Media-Downloads erfordern dasselbe Access Token wie für die API. Sicherstellen, dass es ein permanentes System-User-Token ist, kein temporäres User-Token. Prüfen, dass Token nicht widerrufen wurde. |
messages-Array fehlt in Payload |
Es ist eine Statusaktualisierung — das Array heißt statuses, nicht messages |
Separat auf value.messages UND value.statuses prüfen. Beide kommen unter dem messages-Feld-Abonnement an. |
| Absendernummer hat kein +-Präfix | Cloud API liefert from ohne +-Präfix (z.B. 15550001234 statt +15550001234) |
Beim Empfang normalisieren: '+' + msg.from für E.164-Format. SocialHook normalisiert dies automatisch. |
| Timestamp ist String, nicht Integer | Cloud API liefert timestamp als Unix-Timestamp-String |
Immer parseInt(msg.timestamp, 10) (Node.js) oder int(msg["timestamp"]) (Python) vor Datum-Operationen. |
SocialHook: die verwaltete Webhook-Schicht
Alles in dieser Referenz — HMAC-Verifizierung, Payload-Extraktion aus der verschachtelten Envelope, Timestamp-Parsing, Absender-Normalisierung, Wiederholungsbehandlung, Deduplizierungs-Infrastruktur, WABA- vs. Telefon-Ebenen-Routing — ist Infrastruktur-Arbeit, auf der Ihr Produkt nicht differenziert.
SocialHook übernimmt die gesamte Cloud-API-Webhook-Schicht und liefert ein normalisiertes Ereignis an Ihren Endpunkt, sodass Ihr Anwendungscode nur sauberes, konsistentes JSON sieht — niemals die rohe verschachtelte Meta-Payload mit ihren Eigenheiten.
| Betreff | Rohe Cloud API | SocialHook normalisiert |
|---|---|---|
| Nachrichten-Extraktion | entry[0].changes[0].value.messages[0] | Flaches message-Objekt auf Top-Level |
| Absender-Format | "15550001234" — kein +-Präfix | "+15550001234" — E.164 normalisiert |
| Timestamp | "1747231892" — String | 1747231892 — Integer |
| HMAC-Verifizierung | Sie implementieren es | Erledigt — signature_verified: true |
| Wiederholung bei Ihrer Downtime | Meta wiederholt (7 Tage) | SocialHook wiederholt (3x exponentiell) |
| Multi-Channel-Format | Unterschiedliches Schema pro Plattform | Gleiches Schema: WhatsApp + FB + Instagram |
| Zustell-Logs | Nicht verfügbar | Vollständiges Log pro Ereignis |
| Monatliche Kosten | Ihre Server-Infrastrukturkosten | 50 $ pauschal |
SocialHook deckt WhatsApp, Facebook Messenger und Instagram DMs ab — alle drei Meta-Messaging-Kanäle — unter einem Konto, einer Webhook-URL, einem normalisierten Payload-Format. Siehe die vollständige Payload-Referenz oder starten Sie mit dem 5-Minuten-Quickstart.
Häufige Fragen
msg.id in Redis mit 24h TTL mittels SET ... NX. Prüfen Sie vor der Verarbeitung jedes Ereignisses, ob die ID existiert. Wenn ja, überspringen. Wenn nein, hinzufügen und verarbeiten. Dies verhindert doppelte CRM-Einträge, doppelte AI-Antworten und doppelte Zahlungen.sha256=<64-stelliger kleingeschriebener Hex>. Der Hex-Wert ist HMAC-SHA256 der rohen Request-Body-Bytes, wobei Ihr App Secret als Schlüssel verwendet wird (finden Sie es in App Settings → Basic — dies ist anders als Ihr Access Token). Das sha256=-Präfix vor dem Vergleich entfernen. Immer timing-sicheren Vergleich verwenden.messages-Array, Statusaktualisierungen enthalten ein statuses-Array. Immer prüfen, welches Array vorhanden ist, bevor verarbeitet wird — der Versuch, value.messages[0] bei einer Statusaktualisierungs-Payload zu lesen, gibt undefined zurück.metadata.phone_number_id dispatcht, und Telefonnummer-Ebenen-Webhooks nur dort hinzufügen, wo unterschiedliches Routing benötigt wird.Referenz gelesen.
Jetzt den ersten Webhook empfangen.
Sie kennen die Spezifikation. SocialHook übernimmt die HMAC-Verifizierung, Payload-Normalisierung, Wiederholungslogik und Zustell-Logging — sodass Ihr Anwendungscode nur sauberes JSON sieht. Verbinden Sie Ihre Nummer in unter 5 Minuten.