Dunkler Terminal-Split-Screen mit Node.js-Webhook-Handler mit HMAC-SHA256-Verifizierung links und eingehendem WhatsApp-JSON-Payload rechts
In diesem Leitfaden: So funktioniert die Webhook-Pipeline · Endpunkt erstellen (Node.js + Python) · Verifizierungs-Challenge bearbeiten · HMAC-SHA256-Signaturverifizierung · Jeden Nachrichtentyp parsen · Medien-Downloads handhaben · Produktions-Queue-Muster · SocialHook als verwaltete Schicht · FAQ

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.

Node.js + Express
webhook.js
const express = require('express'); const crypto = require('crypto'); const app = express(); // KRITISCH: Raw-Body parsen VOR express.json() // Sie benötigen den Raw-Buffer für die HMAC-Verifizierung app.use('/webhook', express.raw({ type: '*/*' })); const VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN; // Ihre eigene Zeichenkette const APP_SECRET = process.env.WHATSAPP_APP_SECRET; // aus dem Meta Developer Dashboard // GET — Metas Verifizierungs-Challenge app.get('/webhook', (req, res) => { const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; if (mode === 'subscribe' && token === VERIFY_TOKEN) { console.log('Webhook verifiziert ✓'); res.status(200).send(challenge); // Challenge als Klartext zurückgeben } else { res.sendStatus(403); } }); // POST — eingehende Nachrichtenereignisse app.post('/webhook', (req, res) => { // 1. Signatur ZUERST verifizieren if (!verifySignature(req)) { return res.sendStatus(403); } // 2. Sofort bestätigen — NIEMALS synchron verarbeiten res.sendStatus(200); // 3. Parsen und für asynchrone Verarbeitung in Queue stellen const body = JSON.parse(req.body.toString()); enqueue(body); // Ihre Queue-Funktion — siehe Schritt 6 }); app.listen(3000, () => console.log('Webhook-Server läuft auf :3000'));
Python + FastAPI
webhook.py
import hmac, hashlib, os, json from fastapi import FastAPI, Request, Response, HTTPException from fastapi.responses import PlainTextResponse app = FastAPI() VERIFY_TOKEN = os.environ["WEBHOOK_VERIFY_TOKEN"] APP_SECRET = os.environ["WHATSAPP_APP_SECRET"] # GET — Metas Verifizierungs-Challenge @app.get("/webhook", response_class=PlainTextResponse) async def verify(request: Request): params = request.query_params mode = params.get("hub.mode") token = params.get("hub.verify_token") challenge = params.get("hub.challenge") if mode == "subscribe" and token == VERIFY_TOKEN: return challenge # Klartext, 200 OK raise HTTPException(status_code=403) # POST — eingehende Ereignisse @app.post("/webhook") async def receive(request: Request): raw_body = await request.body() # 1. HMAC ZUERST verifizieren if not verify_signature(request, raw_body): raise HTTPException(status_code=403) # 2. Sofort bestätigen payload = json.loads(raw_body) enqueue(payload) # an asynchronen Worker übergeben return Response(status_code=200)

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 Zeichenkette subscribe
  • hub.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 Wort
  • hub.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.

Häufiger Fehler: Verwendung von 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.

Node.js
verifySignature.js
function verifySignature(req) { const sigHeader = req.headers['x-hub-signature-256']; if (!sigHeader) return false; // 'sha256='-Präfix entfernen const receivedSig = sigHeader.slice(7); // alles nach 'sha256=' // req.body MUSS der raw Buffer sein — siehe express.raw()-Setup oben const expectedSig = crypto .createHmac('sha256', APP_SECRET) .update(req.body) // raw Buffer, NICHT geparstes JSON .digest('hex'); // timingSafeEqual verhindert Timing-Angriffe try { return crypto.timingSafeEqual( Buffer.from(receivedSig, 'hex'), Buffer.from(expectedSig, 'hex') ); } catch { return false; // unterschiedliche Längen werfen — als ungültig behandeln } } module.exports = { verifySignature };
Python
verify.py
import hmac, hashlib def verify_signature(request: Request, raw_body: bytes) -> bool: sig_header = request.headers.get("X-Hub-Signature-256", "") if not sig_header.startswith("sha256="): return False received_sig = sig_header[7:] # 'sha256='-Präfix entfernen expected_sig = hmac.new( APP_SECRET.encode(), raw_body, # raw Bytes, NICHT decodierter String hashlib.sha256 ).hexdigest() # hmac.compare_digest verhindert Timing-Angriffe return hmac.compare_digest(received_sig, expected_sig)
Kritisch: Verwenden Sie rohe Bytes, nicht geparstes JSON. Wenn Sie 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:

Terminal
Tunnel-Optionen
# Option A: ngrok (am häufigsten, erfordert kostenloses Konto) ngrok http 3000 # → https://a1b2c3d4.ngrok-free.app ← Diese in Meta Dashboard einfügen # Option B: Cloudflare Tunnel (kostenlos, kein Zeitlimit, erfordert cloudflared) cloudflared tunnel --url http://localhost:3000 # → https://random-words.trycloudflare.com ← Diese einfügen # Ihre vollständige Webhook-URL zur Registrierung im Meta Dashboard: # https://your-tunnel-url.ngrok-free.app/webhook

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:

  1. Öffnen Sie developers.facebook.com → Ihre App → WhatsApp → Configuration
  2. Klicken Sie unter Webhooks auf Bearbeiten
  3. Fügen Sie Ihre HTTPS-Webhook-URL ein (z. B. https://ihredomain.com/webhook)
  4. Geben Sie Ihr Verify Token ein — die exakte Zeichenkette, die Sie in VERIFY_TOKEN festgelegt haben
  5. Klicken Sie auf Verifizieren und speichern — Meta feuert sofort die GET-Challenge ab. Ihr Server muss innerhalb von ~5 Sekunden antworten.
  6. 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:

JSON
meta-cloud-api-webhook-payload.json
{ "object": "whatsapp_business_account", "entry": [{ "id": "WHATSAPP_BUSINESS_ACCOUNT_ID", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "+44 7700 900 123", // Ihre Nummer "phone_number_id": "PHONE_NUMBER_ID" }, "contacts": [{ "profile": { "name": "Alice" }, "wa_id": "447700900456" // Absender — ohne +-Präfix }], "messages": [{ "from": "447700900456", // Absendertelefon — ohne +-Präfix "id": "wamid.HBgL...", // eindeutige Nachrichten-ID "timestamp": "1747231892", // Unix-String, keine Integer "type": "text", "text": { "body": "Hallo, funktioniert das?" } }] }, "field": "messages" }] }] }

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:

Node.js
parseWebhook.js
function parseWebhookEvent(body) { const value = body?.entry?.[0]?.changes?.[0]?.value; if (!value) return null; const phoneNumberId = value.metadata?.phone_number_id; // Eingehende Nachrichten if (value.messages?.length) { const msg = value.messages[0]; return { kind: 'message', phoneNumberId, // welche Ihrer Nummern hat sie empfangen from: msg.from, // Absender — ohne +-Präfix messageId: msg.id, timestamp: parseInt(msg.timestamp, 10), // String→int konvertieren type: msg.type, // 'text'|'image'|'audio'|usw. raw: msg, // vollständiges Nachrichtenobjekt }; } // Zustell-/Lese-Status-Updates if (value.statuses?.length) { const s = value.statuses[0]; return { kind: 'status', messageId: s.id, status: s.status, // 'sent'|'delivered'|'read'|'failed' recipient: s.recipient_id, }; } return null; }

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:

text
Standard-Textnachricht vom Kunden
Lesen: msg.text.body
image
Vom Kunden gesendetes Foto. Kann optionalen Bildtext enthalten.
Lesen: msg.image.id → herunterladen
audio
Sprachnachricht oder Audiodatei. voice: true wenn in-App aufgenommen.
Lesen: msg.audio.id → herunterladen
document
PDF, Word, Excel oder andere Datei. Enthält Dateinamen.
Lesen: msg.document.id, .filename
video
Videodatei. Kann Bildtext enthalten.
Lesen: msg.video.id → herunterladen
location
Vom Kunden gesetzter Pin. Enthält Breitengrad/Längengrad und optionalen Namen.
Lesen: msg.location.latitude, .longitude
contacts
Vom Kunden geteilte(r) vCard-Kontakt(e).
Lesen: msg.contacts[0].name
reaction
Emoji-Reaktion auf eine Ihrer Nachrichten.
Lesen: msg.reaction.emoji, .message_id
interactive
Button-Klick oder Listenauswahl aus einer WhatsApp Flow-Nachricht.
Lesen: msg.interactive.button_reply.id
Node.js
handleMessage.js
async function handleMessage(msg) { switch (msg.type) { case 'text': await processText(msg.from, msg.text.body); break; case 'image': case 'audio': case 'video': case 'document': { const mediaId = msg[msg.type].id; const filename = msg[msg.type].filename; // nur bei Dokumenten const url = await getMediaUrl(mediaId); await downloadAndStore(url, mediaId, filename); break; } case 'location': await processLocation({ lat: msg.location.latitude, lng: msg.location.longitude, name: msg.location.name, address: msg.location.address, }); break; case 'reaction': await processReaction(msg.reaction.emoji, msg.reaction.message_id); break; case 'interactive': { const type = msg.interactive.type; // 'button_reply' | 'list_reply' const reply = msg.interactive[type]; await processInteractive(type, reply.id, reply.title); break; } default: console.warn(`Nicht behandelter Nachrichtentyp: ${msg.type}`); } }

Schritt 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.

Node.js
downloadMedia.js
const GRAPH_URL = 'https://graph.facebook.com/v21.0'; const ACCESS_TOKEN = process.env.WHATSAPP_ACCESS_TOKEN; // Schritt 1: Medien-ID → temporäre Download-URL auflösen async function getMediaUrl(mediaId) { const res = await fetch( `${GRAPH_URL}/${mediaId}`, { headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } } ); const data = await res.json(); return data.url; // läuft in 5 Minuten ab — JETZT herunterladen } // Schritt 2: Die eigentlichen Datei-Bytes abrufen async function downloadMedia(mediaUrl) { const res = await fetch(mediaUrl, { headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }); return Buffer.from(await res.arrayBuffer()); } // Kombinieren: ID auflösen → herunterladen → auf S3/GCS/ihrem Server speichern async function downloadAndStore(mediaId, filename) { const mediaUrl = await getMediaUrl(mediaId); const buffer = await downloadMedia(mediaUrl); await uploadToStorage(buffer, filename ?? mediaId); // die mediaId oder Ihre Speicher-URL in Ihrer DB für den Konversationsdatensatz 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):

Node.js + BullMQ
queue.js
const { Queue, Worker } = require('bullmq'); const redis = { host: '127.0.0.1', port: 6379 }; const whatsappQueue = new Queue('whatsapp', { connection: redis }); // Wird im POST-Handler aufgerufen — sofort, leichtgewichtig async function enqueue(payload) { await whatsappQueue.add('process-event', payload, { attempts: 3, // bis zu 3 Wiederholungen bei Worker-Fehler backoff: { type: 'exponential', delay: 2000 }, removeOnComplete: 100, // letzte 100 abgeschlossene Jobs für Debugging behalten removeOnFail: 200, }); } // Worker läuft in einem separaten Prozess const worker = new Worker('whatsapp', async (job) => { const event = parseWebhookEvent(job.data); if (!event) return; if (event.kind === 'message') { await handleMessage(event.raw); // Ihre KI / CRM-Logik hier } if (event.kind === 'status') { await updateMessageStatus(event.messageId, event.status); } }, { connection: redis, concurrency: 10 });
Deduplizierung: Meta kann dasselbe Ereignis mehr als einmal zustellen, wenn Ihr Server zu langsam mit der Bestätigung ist. Speichern Sie verarbeitete 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:

JSON
socialhook-normalized-payload.json
{ "platform": "whatsapp", "event": "message.received", "timestamp": 1747231892, // Integer — bereits geparst "from": "+44 7700 900 456", // E.164 — +-Präfix hinzugefügt "conversation_id": "conv_8j3k...", "message": { "type": "text", "body": "Hallo, funktioniert das?", "id": "wamid.HBgL..." }, "signature_verified": true, "delivery": { "attempt": 1, "latency_ms": 41 } }

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

Wie empfange ich WhatsApp-Nachrichten auf meinem Server?
Sie benötigen eine WhatsApp Business-Nummer auf der Cloud API, einen öffentlich erreichbaren HTTPS-Endpunkt und diesen Endpunkt als Ihren Webhook im Meta Developer Dashboard registriert. Meta feuert dann bei jeder eingehenden Nachricht einen HTTP-POST an Ihren Endpunkt. Ihr Server muss innerhalb von 20 Sekunden mit 200 antworten. Siehe die vollständige 9-Schritte-Einrichtung oben.
Warum schlägt meine HMAC-Verifizierung immer wieder fehl?
Fast immer, weil Sie den HMAC auf dem geparsten JSON statt auf den rohen Request-Body-Bytes berechnen. In Express müssen Sie 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.
Wie teste ich meinen Webhook lokal vor dem Deployment?
Verwenden Sie ein Tunneling-Tool. ngrok (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.
Meta wiederholt meinen Webhook ständig — warum?
Drei Ursachen: (1) Ihr Server hat einen Nicht-200-Status zurückgegeben — Signaturverifizierungsfehler geben 403 zurück, Verarbeitungs-Exceptions geben 500 zurück. (2) Ihr Server hat länger als 20 Sekunden für die Antwort gebraucht — verlagern Sie die Verarbeitung in eine asynchrone Queue und geben Sie sofort 200 zurück. (3) Ihr Endpunkt war offline, als Meta das Ereignis gesendet hat — stellen Sie sicher, dass Ihr Server immer läuft, oder verwenden Sie SocialHooks verwaltete Zustellung mit automatischer Wiederholung.
Was ist der Unterschied zwischen Metas rohem Payload und SocialHooks normalisiertem Payload?
Metas Cloud API verpackt jedes Ereignis in eine verschachtelte Struktur: 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.
Wie handhabe ich eingehende Medien (Bilder, Sprachnachrichten, Dokumente)?
Der Webhook-Payload enthält eine Medien-ID, nicht die Datei selbst. Sie müssen: (1) 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.

Keine Kreditkarte erforderlich · 50 $/Monat nach der Testphase · Jederzeit kündbar