InstaTunnel
Navigationsmenu umschalten
Tutorial
11 min read
27 views

WhatsApp Cloud API Webhooks auf localhost: Der Verifizierungs-Token-Handshake, das Retry-Verhalten und die Einrichtung des App-Geheimnisses

Richten Sie WhatsApp Cloud API-Webhooks auf localhost ein. Verarbeiten Sie das hub.challenge-Verifizierungs-Handshake von Meta, prüfen Sie X-Hub-Signature-256, berücksichtigen Sie Wiederholungen und das App-Geheimnis.

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
WhatsApp Cloud API Webhooks auf localhost: Der Verifizierungs-Token-Handshake, das Retry-Verhalten und die Einrichtung des App-Geheimnisses

Quick answer

WhatsApp Cloud API-Webhooks auf localhost: Verifizierungs-Token-Einrichtung: webhook testing answer

For local webhook testing, run your app locally, expose it with a public HTTPS tunnel, and paste the stable callback URL into the provider dashboard.

How do I test webhooks on localhost?

Start your local server, open a public HTTPS tunnel to that port, configure the provider webhook URL, and inspect events in your local logs.

Why does a stable webhook URL matter?

Stable URLs prevent provider dashboards from needing manual callback updates every time you restart a tunnel.

Du startest deinen WhatsApp-Bot auf localhost:3000, öffnest einen Tunnel, fügst die öffentliche URL in das Meta App-Dashboard ein, klickst auf Verifizieren und Speichern, und bekommst eine rote Fehlermeldung. Oder es klappt einmal, du startest den Tunnel am nächsten Morgen neu, und das Dashboard fordert erneut eine Verifizierung.

Beide Probleme haben eine gemeinsame Ursache: Die Webhook-Onboarding von Meta ist ein Handshake, kein einfacher „Sende mir Events“-Schalter. Meta sendet zuerst eine GET-Anfrage mit einem hub.challenge-Wert. Erst wenn du darauf korrekt antwortest, liefert es signierte POST-Anfragen. Wenn du diese nicht mit einem 200 bestätigst, versucht Meta es bis zu 7 Tage lang erneut auf der WhatsApp Business Platform.

Dieser Leitfaden behandelt den Handshake, das Retry-Verhalten und die App-Geheimnis-Einrichtung, und zeigt dann, warum eine stabile Subdomain eine wiederkehrende Aufgabe in eine einmalige Registrierung verwandelt.

Kurzfassung

  • Meta überprüft deine Callback-URL mit einem GET, der hub.mode, hub.verify_token und hub.challenge enthält. Gib HTTP 200 mit dem Challenge-Wert zurück, aber nur, wenn das Token übereinstimmt.
  • Das Verifizierungs-Token ist eine von dir gewählte Zeichenkette; Das App-Geheimnis wird von Meta ausgegeben. Es sind unterschiedliche Anmeldeinformationen.
  • Verifizierung allein liefert nichts. Du musst außerdem das Feld messages abonnieren.
  • WhatsApp wiederholt fehlgeschlagene Zustellungen bis zu 7 Tagen; erwarte Duplikate und veraltete Ereignisse.
  • Meta sendet eine neue Verifizierungs-GET-Anfrage, wann immer du die Callback-URL oder das Verifizierungs-Token festlegst oder änderst. Eine sich ändernde Tunnel-URL bedeutet daher, dass der Handshake wiederholt werden muss. Eine stabile Subdomain bedeutet eine einmalige Registrierung.

Warum Meta localhost nicht aufrufen kann

Meta-Server befinden sich im öffentlichen Internet; dein Laptop sitzt hinter NAT und hat keine eingehenden Ports offen. Meta verlangt außerdem ein gültiges TLS-Zertifikat am Endpunkt, und in der Dokumentation wird angegeben, dass selbstsignierte Zertifikate nicht unterstützt werden.

Für eine lokale Integration braucht es drei Dinge:

  • Ein Server, der sowohl GET (Verifizierung) als auch POST (Events) über denselben Pfad beantwortet.
  • Eine öffentliche HTTPS-URL, die darauf weiterleitet.
  • Weil das Dashboard pro App eine Callback-URL speichert, sollte die URL über Sitzungen hinweg stabil bleiben.

Schritt 1: Das Verifizierungs-Handshake

Im Meta App-Dashboard öffnest du WhatsApp → Configuration. Falls du deine App mit dem Use Case „Connect with customers through WhatsApp“ erstellt hast, befindet sich das Panel stattdessen unter Use cases → Customize → Configuration. Du gibst eine Callback URL und ein Verify token ein. Das Token ist eine von dir gewählte Zeichenkette; Meta stellt es nicht aus. Meta sendet eine Verifizierungsanfrage, wann immer du eines der Felder festlegst oder bearbeitest:

GET /webhook?hub.mode=subscribe\u0026hub.verify_token=YOUR_TOKEN\u0026hub.challenge=1158201444
Parameter Bedeutung
hub.mode Immer subscribe während der Verifizierung
hub.verify_token Der im Dashboard eingegebene Token
hub.challenge Ein zufälliger Wert, von Meta erzeugt

Meta’s Dokumentation legt die Freigabebedingung fest: Antworte mit dem Status 200 und dem hub.challenge-Wert. Wenn du etwas anderes zurückgibst, behandelt Meta den Endpunkt als nicht verifiziert und sendet keine Webhooks.

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 === process.env.VERIFY_TOKEN) {
    // Roher Challenge, Klartext, kein JSON-Wrapper
    return res.status(200).type('text/plain').send(challenge);
  }
  return res.sendStatus(403);
});

In FastAPI kannst du keinen Python-Parameter hub.mode nennen, also verwende Query(alias="hub.mode"). PHP hat eine verwandte Eigenart: Es wandelt Punkte in Abfrageparameternamen in Unterstriche um, lies also hub_mode, hub_verify_token und hub_challenge dort.

Häufige Handshake-Fehler

Symptom Wahrscheinliche Ursache Lösung
„Callback-URL oder Verifizierungs-Token konnte nicht validiert werden“ JSON-Antwort, kein 200-Status oder eine Token-Abweichung von einem Zeichen Gib den rohen Challenge-Wert als Klartext mit Status 200 zurück
Browser zeigt Fehler: Fehlender Parameter Du hast die URL ohne Meta-Abfragezeichenfolge geöffnet Erwartet; nur Meta’s GET trägt hub.challenge
Timeout bei der Verifizierung Server oder Tunnel läuft noch nicht Starte den Server, dann den Tunnel und klicke Verifizieren
Handshake gelingt, nichts kommt an Kein webhook-Feld abonniert Abonniere messages

Der Timeout-Zeile ist localhost-spezifisch. Meta ruft deine URL live auf, wenn du speicherst, daher müssen dein Server und der Tunnel bereits laufen.

Verifizierungs-Token vs. App-Geheimnis

Die beiden Begriffe werden ständig verwechselt, und Meta verwendet sie für unterschiedliche Aufgaben.

  • Das Verifizierungs-Token wählst du aus und verwendest es im GET-Handshake. Hookdeck’s WhatsApp-Leitfaden bemerkt, dass es nur einmal während der Einrichtung genutzt wird.
  • Das App-Geheimnis wird von Meta bereitgestellt, zu finden unter App-Einstellungen → Basic, und es ist der Schlüssel, der jede Event-Lieferung signiert.

Halte sie in separaten Umgebungsvariablen, zum Beispiel VERIFY_TOKEN und META_APP_SECRET. Wenn du das Verifizierungs-Token dort verwendest, wo das App-Geheimnis gehört, wird jede echte Nachricht fehlschlagen.

Jede POST-Anfrage trägt einen X-Hub-Signature-256-Header in der Form sha256=<hex>, eine HMAC-SHA256 des Payloads, der mit deinem App-Geheimnis verknüpft ist. Meta sagt, die Validierung sei optional, aber empfohlen. Berechne den HMAC über die rohen Request-Bytes, bevor irgendeine JSON-Middleware den Body verändert. Hookdeck bemerkt, dass Meta ein escaped-Unicode-Form des Payload signiert, sodass ein geparster und erneut serialisierter Body nicht übereinstimmt.

const crypto = require('crypto');

// express.raw() bewahrt die exakten Bytes, die Meta signiert hat
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.META_APP_SECRET)
    .update(req.body)
    .digest('hex');
  const received = req.get('x-hub-signature-256') || '';

  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  if (!valid) return res.sendStatus(401);

  res.sendStatus(200);               // bestätige zuerst
  queue.push(JSON.parse(req.body));  // anschließend asynchron verarbeiten
});

Die Signatur enthält keinen Zeitstempel. Hookdeck’s Facebook-Webhooks-Leitfaden weist darauf hin, dass ein abgefangenes, gültig signiertes Payload bei erneutem Versand trotzdem validieren würde, daher deduplizierst du anhand der Nachrichten-ID statt die Signatur als Frische-Beweis zu verwenden.

Schritt 2: Abonniere das Feld messages

Verifizierung beweist, dass die URL dir gehört; sie wählt deine Events nicht aus. Nachdem sie erfolgreich war, zeigt das Dashboard eine Liste von Webhook-Feldern. Für einen Chatbot oder Inbox abonniere messages, das eingehende Kunden-Nachrichten und den Status der von dir gesendeten Nachrichten (gesendet, zugestellt, gelesen, fehlgeschlagen) trägt.

Meta-Dokumentation listet die Berechtigungen auf: whatsapp_business_messaging für messages-Webhooks und whatsapp_business_management für alle anderen Webhooks.

Wenn Dashboard-Testevents ankommen, aber echte Nachrichten nicht, prüfe, ob deine App auch beim WhatsApp Business Account selbst abonniert ist. Hookdeck und Webhook Relay berichten dieses Fehler-Szenario: Die URL ist verifiziert, aber die App wurde nie beim WABA abonniert. Die Lösung ist POST /{WABA_ID}/subscribed_apps, und GET auf dem gleichen Endpunkt bestätigt es. Bestätige auch, dass deine App im Live-Modus ist, denn Meta weist darauf hin, dass einige Webhooks nicht an Apps im Dev-Modus gesendet werden.

Schritt 3: Wiederholungen, Duplikate und das 7-Tage-Fenster

Meta behandelt alles außer HTTP 200 als fehlgeschlagene Zustellung. Das Wiederholungsfenster hängt vom Produkt ab:

  • WhatsApp Business Platform: Die Meta-Dokumentation besagt, dass Zustellungen sofort erneut versucht werden und danach mit abnehmender Frequenz bis zu 7 Tagen erneut versucht werden; danach werden unbestätigte Webhooks verworfen.
  • Generic Graph API Webhooks: Metas „Get started with webhooks“-Seite beschreibt Wiederholungen über 36 Stunden, danach werden unbestätigte Updates verworfen.

Diese beschreiben verschiedene Produkte und widersprechen einander nicht. Für WhatsApp plant man rund um 7 Tage. Eine Ausnahme: Bei Nummern mit Meta’s „No Storage“-Konfiguration sind Webhook-Wiederholungen auf 1 Stunde begrenzt, und ein nicht zustellbares Incoming-Message-Webhook wird mit dem Fehlercode 131035 verworfen.

Meta warnt auch, dass Wiederholungen an alle Apps gesendet werden, die den WABA-Webhooks abonniert sind, was zu doppelten Benachrichtigungen führen kann. Das hat vier Folgen.

Zuerst bestätigen, dann arbeiten. Gib 200 zurück, und führe langsame Arbeiten (Datenbank-Schreibvorgänge, LLM-Aufrufe, Antworten) aus einer Queue heraus. Meta veröffentlicht keinen universellen Timeout, und BSP-Dokumente variieren. 360dialog z. B. dokumentiert eine harte Fünf-Sekunden-Grenze für eine 200-Antwort, halte also deinen Handler schnell.

Deduplizieren. Schlüssel-Eingangs-Nachrichten über die Nachrichten-ID (wamid...) und Status-Ereignisse über Status-ID plus Statuswert, da eine ausgehende Nachricht legitim sent, delivered und read erzeugt.

Glaube nicht an die Ankunftsreihenfolge. Hookdeck merkt an, dass WhatsApp keine Reihenfolge-Garantie bietet, daher kann ein read-Status vor delivered eintreffen. Verwende das timestamp-Feld in jedem Event statt deiner Empfangszeit.

Veraltete Ereignisse nach lokalen Ausfällen erwarten. Wenn dein Laptop über Nacht geschlossen ist und der Tunnel ausfällt, versucht Meta weiterhin, die registrierte URL aufzurufen, und beim Wiederverbinden kann eine Flut alter Ereignisse eintreffen. Erhöhe auch deine Timeouts. Meta gibt an, dass Webhook-Payloads bis zu 3 MB groß sein können, und ein 413-Fehler von deinem Server zählt als fehlgeschlagene Zustellung, die Meta erneut versucht.

Meta bietet keine API zum Abrufen historischer Webhook-Daten, daher protokolliere rohe Payloads beim Empfang, falls du sie später brauchst.

Warum eine stabile Subdomain den Workflow verändert

Meta speichert pro App eine Callback-URL, und das Ändern wiederholt den Handshake. Wenn dein Tunnel bei jedem Start einen zufälligen Hostnamen ausgibt, wird jeder Morgen zu: Server starten, Tunnel starten, URL kopieren, in das Dashboard einfügen, auf Verifizieren klicken, während dein Server läuft, und hoffen, dass der Token kein Tippfehler ist. Hookdeck macht im WhatsApp-Guide denselben Punkt zu Tunneln: Die URL ändert sich bei jedem Neustart, es sei denn, du bezahlst für eine feste Subdomain.

Eine stabile Subdomain reduziert das auf eine einzige Registrierung. Verifiziere https://yourname.example-tunnel.dev/webhook einmal, und du kannst den Tunnel neu starten, neu booten oder zwischen Laptops wechseln, ohne Meta’s Konfiguration zu ändern, solange derselbe Name auf deine Maschine zeigt. Tunnel-Dienste mit reservierten Subdomains, InstaTunnel darunter, existieren aus diesem Grund. Prüfe, dass der Name auch nach Neustarts bestehen bleibt, nicht nur bei Verbindungen innerhalb einer Sitzung.

Entwicklung und Produktion sauber durch Overrides trennen

Wenn eine Meta-App beide Umgebungen bedient, tausche die app-level URL beim Wechsel nicht aus. Meta unterstützt alternative Callback-URLs pro WABA oder pro Geschäftsnummer. Wenn ein unterstütztes Feld ausgelöst wird, prüft Meta zuerst die Telefonnummer, dann die WABA, und greift dann auf die Callback-URL der App zurück.

curl -X POST "https://graph.facebook.com/$GRAPH_API_VERSION/$TEST_WABA_ID/subscribed_apps" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "override_callback_uri": "https://dev.example-tunnel.dev/webhook",
    "verify_token": "'"$DEV_VERIFY_TOKEN"'"
  }'

Für eine einzelne Nummer sende ein webhook_configuration-Objekt mit denselben zwei Feldern an POST /{PHONE_NUMBER_ID}. Laut Meta’s Override-Dokumentation:

  • Die URL ist auf 200 Zeichen begrenzt, und sowohl die URL als auch ein Verifizierungs-Token sind erforderlich.
  • Overrides gelten nur für bestimmte Felder, einschließlich messages. Template-Webhooks (wie message_template_status_update) und account-level webhooks (wie account_update) gehen immer zur App-URL.
  • Du kannst die aktive Konfiguration mit GET /{PHONE_NUMBER_ID}?fields=webhook_configuration prüfen, die Telefonnummer, die WABA- und Anwendungs-URLs zeigt.
  • Wenn du die App erneut beim WABA abonnierst, ohne Body-Parameter, entfernt dies eine WABA-Level-Override.

Verwende die Graph-API-Version, auf die deine App abzielt, anstelle von $GRAPH_API_VERSION. Ein permanenter dev.-Hostname ist ein ideales Overrides-Ziel für deine Testnummer, während die Produktion ihre eigene URL behält.

Hinweis zur 2026 mTLS-Änderung

Wenn du Mutual TLS (mTLS) für dein Webhook-Abonnement aktiviert hast, kündigt Meta an, dass ab dem 31. März 2026 das Client-Zertifikat von einer neuen Meta-eigenen CA signiert wird. Der Common Name bleibt client.webhooks.fbclientcerts.com. Die Ankündigung von Graph API v25 erklärte, dass das vorher DigiCert-signierte Zertifikat nicht erneuert werden konnte, weil DigiCert die Client-Authentication EKU auslaufen lässt. Das Zertifikat-Root ist am 15. April 2026 abgelaufen. Entwickler sollten vor dem Stichtag meta-outbound-api-ca-2025-12.pem in ihren Trust Store aufnehmen. Ein Server, der mTLS erzwingt und dieses Update auslässt, würde keine Webhooks mehr erhalten; Lösung ist eine Trust-Store-Änderung.

Meta gibt zudem an, dass mTLS pro Anwendung aktiviert ist, nicht pro Messaging-Konto oder Telefonnummer. Die meisten localhost-Setups verwenden kein mTLS. Wenn du eingehenden Traffic einschränken willst, empfiehlt Meta, mTLS statt einer IP-Whitelist zu verwenden, da sich diese Adressen regelmäßig ändern.

Fehlerbehebung: Webhook empfängt keine Nachrichten

  1. Handshake manuell testen. Fordere deine URL mit ?hub.mode=subscribe\u0026hub.verify_token=YOUR_TOKEN\u0026hub.challenge=123 an und bestätige, dass 123 als Klartext mit Status 200 zurückkommt.
  2. Tokens Zeichen für Zeichen vergleichen, einschließlich trailing spaces.
  3. Bestätige das messages-Abonnement, dann prüfe GET /{WABA_ID}/subscribed_apps.
  4. Prüfe den App-Modus. Einige Webhooks werden nicht gesendet, solange die App im Dev-Modus ist.
  5. Suche nach einem Override, der deine App-URL überschreibt.
  6. Prüfe den Tunnel. Wiederholungen lassen einen ausgefallenen Tunnel wie ein Meta-Problem wirken.
  7. Prüfe, dass die Signaturvalidierung gültige POSTs nicht ablehnt wegen eines falschen Secrets oder eines neu serialisierten Bodys.

FAQ

Was ist das WhatsApp-Webhook-Verifizierungs-Token?

Eine Zeichenkette, die du wählst und im Meta App Dashboard eingibst. Meta sendet sie zurück als hub.verify_token, und dein Server vergleicht sie mit dem gespeicherten Wert, bevor er hub.challenge ausgibt.

Was sollte der Verifizierungs-Endpunkt zurückgeben?

HTTP 200 mit dem Wert von hub.challenge als Antworttext. Dies als Klartext zurückzugeben, ohne JSON-Wrapper, ist der sichere Weg. Jede andere Antwort lässt den Endpunkt als nicht verifiziert gelten.

Wie lange wiederholt Meta fehlgeschlagene WhatsApp-Webhooks?

Meta’s WhatsApp-Dokumentation sagt bis zu 7 Tagen nach, mit abnehmender Frequenz (1 Stunde für No Storage-Nummern). Generische Graph API-Webhooks verwenden ein kürzeres Fenster von 36 Stunden.

Muss ich mich erneut verifizieren, wenn ich meinen lokalen Port ändere?

Nein, vorausgesetzt, die öffentliche URL und der Pfad bleiben gleich, denn Meta kennt nur die öffentliche Adresse. Meta verifiziert erneut, wann immer du die Callback-URL oder das Verifizierungs-Token festlegst oder bearbeitest, daher bedeutet ein neuer Hostname oder Pfad einen neuen Handshake.

Fazit

Das Modell von Meta besteht aus drei Teilen: einem GET-Handshake, der durch ein von dir erfundenes Verifizierungs-Token gesichert ist, POST-Lieferungen, die mit einem von Meta ausgegebenen App-Geheimnis signiert sind, und einem 7-Tage-Wiederholungsnetz, das schnelle Bestätigungen und idempotente Handler belohnt. Die Entwicklung auf localhost ist schmerzhaft, nur wenn sich die öffentliche URL ständig ändert. Behebe das mit einer stabilen Subdomain, registriere einmal, abonniere messages und halte Verifizierungs-Token und App-Geheimnis getrennt.

Quellen

Continue from this article into the most relevant product guides and workflows.

Related Topics

#whatsapp cloud api webhook#whatsapp cloud api webhooks localhost#whatsapp webhook verify token#hub.challenge#hub.verify_token#whatsapp webhook verification#meta webhook verification#whatsapp webhook handshake#X-Hub-Signature-256#whatsapp webhook signature validation#verify webhook signature#meta app secret#whatsapp app secret#whatsapp webhook callback url#whatsapp webhook localhost#test whatsapp webhook locally#whatsapp webhook local development#localhost tunnel#localhost tunneling#expose localhost to internet#ngrok alternative#stable subdomain tunnel#static tunnel subdomain#persistent callback url#webhook testing localhost#webhook debugging#webhook retries#meta webhook retries#whatsapp webhook 200 response#whatsapp webhook not working#whatsapp webhook verification failed#whatsapp webhook GET request#whatsapp webhook POST request#whatsapp business api#whatsapp business platform#meta for developers#meta developer app#facebook developers webhook#whatsapp api tutorial#whatsapp api node.js#whatsapp webhook express#whatsapp webhook python flask#whatsapp webhook fastapi#whatsapp chatbot development#whatsapp bot webhook#whatsapp message webhook#whatsapp incoming messages webhook#HMAC SHA256 webhook#webhook security#webhook authentication#webhook signature verification#developer tools#devops#api integration#cloud native#backend development#webhook tutorial#instatunnel

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles