So funktioniert die Pipeline für eingehende WhatsApp-Nachrichten
Die meisten Tutorials beginnen mit Code, bevor sie die Architektur erklären. Deshalb haben die meisten Implementierungen Fehler. Hier ist die vollständige Pipeline — jeder Hop, den eine Nachricht macht, bevor sie Ihre Anwendungslogik erreicht:
Vier Dinge müssen zutreffen, damit diese Pipeline zuverlässig funktioniert:
- Ihr Endpunkt ist öffentlich über HTTPS erreichbar. Kein localhost, kein HTTP. Meta lehnt Nicht-HTTPS-URLs ab und kann private IPs nicht erreichen.
- Ihr Endpunkt antwortet innerhalb von 20 Sekunden mit 200. Alles, was langsamer ist, führt dazu, dass Meta die Zustellung wiederholt. Wenn es oft genug wiederholt wird, stellt es das Senden an Ihren Endpunkt vollständig ein.
- Sie verifizieren die HMAC-SHA256-Signatur bei jeder Anfrage. Ohne diese kann jeder Angreifer, der Ihre Webhook-URL entdeckt, gefälschte Nachrichten an Ihr System senden.
- Sie bestätigen zuerst, verarbeiten zweitens. Geben Sie sofort 200 zurück, schieben Sie den Payload in eine Queue, verarbeiten Sie asynchron. Führen Sie niemals schwere Arbeit synchron im Webhook-Handler aus.
Schritt 1: Den Webhook-Endpunkt erstellen
Ihr Endpunkt muss zwei HTTP-Methoden auf derselben URL handhaben: GET (Metas einmalige Verifizierungs-Challenge) und POST (Live-Nachrichtenereignisse). Hier ist die minimal funktionierende Implementierung sowohl in Node.js als auch in Python.
Schritt 2: Die Verifizierungs-Challenge korrekt handhaben
Wenn Sie Ihre Webhook-URL im Meta Developer Dashboard registrieren, sendet Meta eine einmalige GET-Anfrage, um zu verifizieren, dass Sie den Endpunkt kontrollieren. Dies ist der Schritt, an dem fast jede Erstimplementierung scheitert — meist weil Entwickler JSON statt Klartext zurückgeben oder das gesamte Params-Objekt statt nur des Challenge-Werts zurückgeben.
Die GET-Anfrage von Meta enthält drei Query-Parameter:
hub.mode— immer die Zeichenkettesubscribehub.verify_token— die Zeichenkette, die Sie im Meta Dashboard festlegen. Sie wählen diesen Wert — machen Sie ihn zu einem zufälligen Geheimnis, nicht zu einem vorhersagbaren Worthub.challenge— eine zufällige Zahl, die Meta als Klartext zurückhaben möchte
Ihr Endpunkt muss: (1) prüfen, dass hub.mode === 'subscribe', (2) prüfen, dass hub.verify_token mit Ihrem erwarteten Wert übereinstimmt, (3) hub.challenge als Response-Body mit Content-Type: text/plain und Status 200 zurückgeben. Geben Sie JSON zurück, geben Sie den falschen Wert zurück oder geben Sie einen Nicht-200-Status zurück — die Verifizierung schlägt fehl und Meta markiert Ihren Webhook als nicht verifiziert.
res.json({ challenge }) in Express statt res.send(challenge). Meta erwartet Klartext. Das JSON-Wrapping der Challenge führt dazu, dass die Verifizierung fehlschlägt, obwohl Ihr Server 200 zurückgibt.Schritt 3: HMAC-SHA256-Signaturverifizierung — der Schritt, den niemand in der Produktion überspringt
Ihre Webhook-URL ist öffentlich. Jeder, der sie findet, kann gefälschte Payloads an Ihren Server POSTen. Ohne Signaturverifizierung könnte Ihr KI-Agent mit fabrizierten Konversationen gefüttert werden, Ihr CRM mit erfundenen Kontakten vergiftet werden und Ihre Logik könnte nach Belieben von einem Angreifer ausgelöst werden.
Meta signiert jede POST-Anfrage mit Ihrem App Secret (zu finden im Meta Developer Dashboard → App Settings → Basic). Die Signatur befindet sich im Header X-Hub-Signature-256, formatiert als sha256=<hex_digest>. Der Digest ist HMAC-SHA256 der rohen Request-Body-Bytes unter Verwendung Ihres App Secret als Schlüssel.
JSON.parse() aufrufen, bevor Sie den HMAC berechnen, wird der Digest niemals übereinstimmen — JSON-Serialisierung kann Leerzeichen und Schlüsselreihenfolge verändern. Sie müssen den HMAC auf den exakten Bytes berechnen, die im Request-Body angekommen sind. In Express bedeutet dies, express.raw() auf Ihrer Webhook-Route vor jedem anderen Body-Parser einzurichten. In FastAPI rufen Sie await request.body() auf, bevor Sie eine JSON-Deserialisierung durchführen.Schritt 4: Lokale Entwicklung — localhost für Meta freigeben
Meta kann http://localhost:3000 nicht erreichen. Während der Entwicklung benötigen Sie eine öffentliche HTTPS-URL, die Traffic zu Ihrem lokalen Server tunnelt. Zwei zuverlässige Optionen:
Registrieren Sie die Tunnel-URL im Meta Developer Dashboard (WhatsApp → Configuration → Webhooks). Wenn Sie ngrok neu starten, generiert es eine neue URL — aktualisieren Sie das Dashboard jedes Mal. Cloudflare Tunnel bleibt bei benannten Tunneln über Neustarts hinweg bestehen. Wechseln Sie vor dem Live-Gang auf Ihre Produktions-Domain-URL.
Schritt 5: Den Webhook im Meta Developer Dashboard registrieren
Wenn Ihr Endpunkt läuft und öffentlich erreichbar ist:
- Öffnen Sie developers.facebook.com → Ihre App → WhatsApp → Configuration
- Klicken Sie unter Webhooks auf Bearbeiten
- Fügen Sie Ihre HTTPS-Webhook-URL ein (z. B.
https://ihredomain.com/webhook) - Geben Sie Ihr Verify Token ein — die exakte Zeichenkette, die Sie in
VERIFY_TOKENfestgelegt haben - Klicken Sie auf Verifizieren und speichern — Meta feuert sofort die GET-Challenge ab. Ihr Server muss innerhalb von ~5 Sekunden antworten.
- Nach dem Speichern klicken Sie neben dem Webhook auf Verwalten und aktivieren das Feld-Abonnement messages. Ohne dieses pusht Meta keine eingehenden Nachrichtenereignisse an Ihren Endpunkt.
Schritt 6: Den Cloud-API-Webhook-Payload parsen
Sobald die Verifizierung abgeschlossen ist, löst jede eingehende Kundennachricht einen POST an Ihren Endpunkt aus. Hier ist die vollständige Struktur eines Cloud-API-Textnachrichten-Ereignisses — und wie Sie darin navigieren:
Die Nachricht, die Sie interessiert, ist unter body.entry[0].changes[0].value.messages[0] vergraben. Diese Verschachtelung überrascht jeden. Ein Status-Update-Ereignis (Zustellungsbestätigung) kommt im gleichen Umschlag an, hat aber ein statuses-Array statt messages. Hier ist das Extraktionsmuster:
Schritt 7: Alle WhatsApp-Nachrichtentypen handhaben
Das Feld type sagt Ihnen, welche Eigenschaft Sie lesen müssen. Jeder Typ hat eine andere Struktur. Hier ist die vollständige Zuordnung:
msg.text.bodymsg.image.id → herunterladenvoice: true wenn in-App aufgenommen.msg.audio.id → herunterladenmsg.document.id, .filenamemsg.video.id → herunterladenmsg.location.latitude, .longitudemsg.contacts[0].namemsg.reaction.emoji, .message_idmsg.interactive.button_reply.idSchritt 8: Eingehende Medien herunterladen — der Zwei-Schritt-Fetch
Wenn ein Kunde ein Bild, eine Sprachnachricht oder ein Dokument sendet, enthält der Webhook-Payload nicht die Datei — er enthält eine Medien-ID. Sie müssen einen separaten API-Aufruf tätigen, um die ID in eine temporäre Download-URL aufzulösen, und dann die eigentliche Datei abrufen. Die Download-URL läuft nach 5 Minuten ab — sofort herunterladen und auf Ihrer eigenen Infrastruktur speichern.
Schritt 9: Queue-Architektur — niemals im Webhook-Handler verarbeiten
Dies ist das Produktionsmuster, das eine Spielzeug-Implementierung von einer trennt, die echtem Traffic standhält. Die Regel ist absolut: Ihr Webhook-Handler muss innerhalb von 20 Sekunden mit 200 antworten. Wenn Sie einen LLM aufrufen, ein CRM abfragen oder Medien synchron im Handler herunterladen, werden Sie irgendwann unter Last timeouten, Meta wird wiederholen, und Sie werden doppelte Ereignisse verarbeiten.
Das korrekte Muster: Sofort bestätigen, den rohen Payload in eine Queue schieben, asynchron in einem Worker verarbeiten. Hier ist ein minimales BullMQ-Muster (Redis-basiert):
msg.id-Werte in Redis mit einer kurzen TTL (z. B. 24h). Prüfen Sie vor der Verarbeitung eines Jobs, ob msg.id bereits in Ihrer Deduplizierungs-Menge enthalten ist. Wenn ja, überspringen. Wenn nicht, hinzufügen und verarbeiten. Dies verhindert Doppelantworten Ihres KI-Agenten und doppelte CRM-Datensätze.Alternative: All das oben Genannte mit SocialHook überspringen
Alles oben Genannte — Handhabung der Verifizierungs-Challenge, HMAC-SHA256-Signierung, Payload-Extraktion aus der verschachtelten Struktur entry[0].changes[0].value, Medien-ID-Auflösung, Wiederholungslogik, Deduplizierung — ist Infrastrukturarbeit. Es ist nicht Ihr Produkt. Es ist das Gerüst, auf dem Ihr Produkt läuft.
SocialHook ersetzt diese gesamte Schicht. Sie verbinden Ihre WhatsApp Business-Nummer mit SocialHook, fügen die Webhook-URL Ihres Servers im SocialHook-Dashboard ein, und SocialHook:
- Handhabt Metas Verifizierungs-Challenge automatisch
- Verifiziert die HMAC-SHA256-Signatur bei jedem eingehenden Ereignis
- Extrahiert die Nachricht aus der verschachtelten Cloud-API-Hülle
- Normalisiert den Payload zu einem konsistenten Format über WhatsApp, Facebook Messenger und Instagram-DMs
- Wiederholt die Zustellung an Ihren Endpunkt bis zu 3-mal mit exponentiellem Backoff, wenn Ihr Server einen Nicht-200-Status zurückgibt
- Protokolliert jeden Zustellversuch mit Zeitstempel, Status-Code und Latenz
- Liefert an Ihren Endpunkt in unter 50 ms
Ihr Endpunkt erhält dies anstelle von Metas rohem, verschachteltem Payload:
Keine verschachtelte Traversierung. Kein parseInt(timestamp). Kein fehlendes +-Präfix bei der Absendernummer. Keine plattformspezifischen Switch-Anweisungen — dieselbe Payload-Struktur kommt an, egal ob der Kunde Ihnen auf WhatsApp, Facebook oder Instagram eine Nachricht geschickt hat. Ein Webhook-Handler, drei Kanäle. Die Gesamtkosten betragen pauschal 50 $/Monat — weniger als eine Stunde Engineering-Zeit, um das Äquivalent von Grund auf zu bauen und zu warten.
Häufige Fragen
express.raw() auf Ihrer Webhook-Route vor jedem anderen Body-Parser einrichten. In FastAPI rufen Sie await request.body() vor der Deserialisierung auf. Prüfen Sie auch, dass Sie Ihr App Secret (aus App Settings → Basic) und nicht Ihr Access Token als HMAC-Schlüssel verwenden — das sind unterschiedliche Werte.ngrok http 3000) ist am gebräuchlichsten — es generiert eine öffentliche HTTPS-URL, die an Ihren lokalen Port weiterleitet. Cloudflare Tunnel (cloudflared tunnel --url http://localhost:3000) ist eine kostenlose Alternative ohne Sitzungszeitlimits. Fügen Sie die generierte URL in das Webhook-Feld des Meta Developer Dashboards ein. Denken Sie daran, sie zu aktualisieren, wenn Sie ngrok neu starten, da sich die URL ändert.body.entry[0].changes[0].value.messages[0]. Der Zeitstempel ist ein String, der Absender hat kein +-Präfix, und das Format unterscheidet sich von Facebook-Messenger- und Instagram-Webhooks. SocialHook extrahiert die Nachricht, normalisiert den Absender ins E.164-Format, konvertiert den Zeitstempel in einen Integer und liefert dieselbe flache JSON-Struktur über alle drei Meta-Kanäle hinweg. Sie schreiben einen Parser und er funktioniert für WhatsApp, Facebook und Instagram.GET https://graph.facebook.com/v21.0/{media_id} mit Ihrem Access Token aufrufen, um eine temporäre Download-URL zu erhalten, (2) die Datei von dieser URL abrufen (ebenfalls mit Ihrem Access Token im Authorization-Header), (3) die Datei auf Ihrem eigenen Server oder Cloud-Speicher speichern. Die temporäre URL läuft in etwa 5 Minuten ab — sofort herunterladen. Siehe den vollständigen Node.js-Code im Medien-Abschnitt oben.Nummer verbinden.
Erster Webhook in 5 Minuten.
Sie haben die vollständige Implementierung gelesen. Wenn Sie HMAC-Verifizierung, Wiederholungslogik und Payload-Normalisierung nicht selbst verwalten möchten — SocialHook übernimmt all das für Sie. Fügen Sie Ihre Endpoint-URL ein. Erhalten Sie sauberes JSON. Pauschal 50 $/Monat.