Wenn ein WhatsApp-Bot nach der Demo schweigt, liegt es selten am Modell
Hören WhatsApp-Bots nach dem ersten Test auf zu antworten, ist meist nicht die Prompt-Qualität schuld, sondern das Webhook-Design. Meta verlangt einen verifizierten öffentlichen HTTPS-Endpunkt, Antworten müssen das 24-Stunden-Service-Fenster respektieren, und langwierige KI-Aufgaben gehören nicht in den Webhook.
Veröffentlicht am 16. Oktober • Zuerst veröffentlicht auf standardcompute.com
Wenn dein WhatsApp-Bot nach der Demo nicht mehr antwortet, liegt das Problem meistens nicht an der Qualität der Prompts.
Meta verlangt einen verifizierten öffentlichen HTTPS-Endpunkt. Antworten müssen das 24-Stunden-Servicefenster einhalten. Medien kommen als IDs an, nicht als Dateien. Und wenn du langsamen KI-Arbeit innerhalb des Webhook-Requests erledigst, bereitest du dich selbst auf zufälliges Schweigen vor.
Manche erhielten Antworten. Manche bekamen gar nichts. Eine Person schickte ein Bild und wurde ignoriert. Eine andere erhielt doppelte Antworten. Wieder jemand kam am nächsten Morgen zurück und der Bot tat so, als hätte es die Konversation nie gegeben.
Mein erster Instinkt war derselbe wie bei den meisten KI-Entwicklern:
WhatsApp-Bots sind nicht hauptsächlich ein KI-Problem. Sie sind ein Problem der Workflow-Zuverlässigkeit.
Sobald ich das akzeptiert hatte, wurde die Architektur deutlich besser.
Ein Demo-Pfad für einen WhatsApp-Bot ist fast immer zu sauber.
Du sendest einen kurzen Text. Der Webhook empfängt ihn. Deine App ruft die WhatsApp Cloud API auf. Der Bot antwortet. Alle fühlen sich schlau.
Genau dieser Ablauf verbirgt die Dinge, die in der Produktion brechend sind:
Man entfernt jeden schwierigen Teil und redet sich dann ein, die Architektur sei in Ordnung.
Zumindest bis Nutzer anfingen, Bilder, PDFs, Sprachnachrichten und Stunden später folgende Nachrichten zu senden.
Meta ist streng, was das Webhook-Setup angeht, und ehrlich gesagt ist das auch richtig so.
Für die WhatsApp Cloud API muss dein Webhook:
Wenn die Verifizierung falsch ist, sendet Meta keine Webhooks, mehr oder weniger.
Der Verifizierungs-Request sieht so aus:
GET <CALLBACK_URL>?hub.mode=subscribe&hub.challenge=<HUB_CHALLENGE>&hub.verify_token=<HUB_VERIFY_TOKEN> Enter fullscreen mode Exit fullscreen mode Dein Server muss Folgendes zurückgeben:
import express from " express "; const app = express (); app. use ( express. json ()); const VERIFY_TOKEN = process. env. WHATSAPP_VERIFY_TOKEN; app. get ( " /webhook ", ( req, res ) => { const mode = req. query [ " hub.mode " ]; const challenge = req. query [ " hub.challenge " ]; const token = req. query [ " hub.verify_token " ]; if ( mode === " subscribe " && token === VERIFY_TOKEN ) { return res. status ( 200 ). send ( challenge ); } return res. sendStatus ( 403 ); }); Enter fullscreen mode Exit fullscreen mode Dieser Teil hat mich einmal erwischt.
Aber der größere Fehler zeigte sich nach der Verifizierung.
Ich habe zu viel Arbeit innerhalb des Webhook-Requests selbst erledigt.
Eine Nachricht kam herein, und dann machte mein Ablauf Folgendes:
Wenn dein Webhook auf GPT-5, Claude, OCR, HubSpot, Airtable oder Google Sheets wartet, hast du aus einem Endpunkt zum Empfang von Nachrichten einen Flaschenhals eines verteilten Systems gemacht.
Deshalb wirken Bots in der Produktion so zufällig.
WhatsApp webhook -> verify + parse -> persist event -> return 200 -> enqueue job Worker -> fetch media -> run AI -> write CRM/logs -> send reply Enter fullscreen mode Exit fullscreen mode Wenn du eine völlig simple lokale Variante willst, ist selbst eine Datenbanktabelle plus ein Polling-Worker besser, als den Webhook auf die LLM-Latenz zu blockieren.
Das hier hat dumm viel Zeit gekostet.
Ich habe eine Antwort über die Graph API gesendet und eine normale Erfolgsmeldung erhalten.
Also ging ich davon aus, dass die Nachricht unterwegs war.
Bei der WhatsApp Cloud API können Fehler auftauchen:
Wenn du nur die erste Variante loggst, kann dein Dashboard gesund aussehen, während die Nutzer gar nichts sehen.
Ich hatte Observability für Requests, die ich selbst initiiert hatte. Ich hatte keine Observability für Zustell-Events, die Meta später zurückgeschickt hat.
Sobald ich ausgehendes Messaging als Event-Stream statt als einzelnen API-Aufruf behandelt habe, wurde das Debugging viel einfacher.
Rohe WhatsApp-Payloads sind konsistent, aber es macht keinen Spaß, sie direkt in die App-Logik weiterzuleiten.
In der Regel interessieren dich Felder, die in einer Struktur wie dieser vergraben sind:
Statt diese Rohtorm direkt in n8n, Make oder eigene Handler zu verdrahten, normalisiere ich sie zuerst.
function normalizeWhatsAppMessage ( payload ) { const change = payload. entry?.[ 0 ]?. changes?.[ 0 ]?. value; const msg = change?. messages?.[ 0 ]; if (! msg ) return null; return { provider: " whatsapp ", messageId: msg. id, from: msg. from, timestamp: Number ( msg. timestamp ), type: msg. type, text: msg. text?. body?? null, imageId: msg. image?. id?? null, audioId: msg. audio?. id?? null, documentId: msg. document?. id?? null }; } Enter fullscreen mode Exit fullscreen mode Diese eine Schicht hat Retries, Deduplizierung und die Medienbehandlung deutlich weniger chaotisch gemacht.
Textnachrichten schmeicheln deiner Architektur.
Sobald ein Nutzer zum ersten Mal ein Bild, ein PDF oder einen Audio-Clip sendet, wird dein "einfacher Chatbot" zu einem echten Integrationsprojekt.
Eingehende Medien-Webhooks enthalten die Datei nicht.
Viele Demos überspringen das, weil sie nur Text testen.
async function fetchMediaUrl ( mediaId, accessToken ) { const res = await fetch ( `https://graph.facebook.com/v23.0/ ${ mediaId } `, { headers: { Authorization: `Bearer ${ accessToken } ` } }); if (! res. ok ) { throw new Error ( `Failed to fetch media metadata: ${ res. status } ` ); } return res. json (); } Enter fullscreen mode Exit fullscreen mode Und ja, auch hier läuft wieder eine Uhr.
Wenn du das Abrufen von Medien verzögerst, wird deine Funktion „unterstützt Bilder“ schnell fragil.
Ich hatte einen Ablauf, bei dem die Bildanalyse hinter Jobs mit niedrigerer Priorität lag. Auf dem Papier sah das elegant aus. In der Praxis war es ein Zuverlässigkeitsfehler.
Medien sofort abrufen, speichern, was du brauchst, und die langsamere KI-Arbeit später erledigen.
Genau hier sagen Leute: „WhatsApp hat zufällig aufgehört zu antworten.“
Deine Nachrichtenrichtlinie hat sich unter dir geändert.
WhatsApp hat ein 24-Stunden-Kundenservicefenster.
Es beginnt, wenn ein Nutzer dir schreibt oder dich anruft, und jede neue Nutzernachricht setzt den Timer zurück.
Innerhalb dieses Fensters kannst du freie Antworten senden. Außerhalb davon benötigst du eine genehmigte Vorlage.
Also ja, dein Bot kann beim Testen perfekt aussehen und in der Produktion dann „ausfallen“, sobald echte Gespräche aus dem Zeitfenster fallen.
Das ist kein Randfall. Das ist Laufzeitverhalten.
Sobald ich begann, Richtlinienregeln als Teil des Systemdesigns zu behandeln, wurde der Bot viel leichter zu durchschauen.
Meta dokumentiert Drosselungsfehler wie 130429 für Rate Limits.
Wichtig ist, dass du Fehler anhand der Felder code und details behandelst, nicht nur anhand des HTTP-Status.
Eine dumme Retry-Schleife kann aus einem vorübergehenden Problem drei neue machen:
Ein eingehendes Ereignis wurde zweimal verarbeitet. Mein Worker hat einen ausgehenden Versand zu aggressiv wiederholt. Der Nutzer erhielt doppelte Nachrichten. Meine Logs sahen aus wie ein Verschwörungsbrett.
async function shouldProcessMessage ( redis, messageId ) { const key = `wa:msg: ${ messageId } `; const wasSet = await redis. set ( key, " 1 ", { NX: true, EX: 60 * 60 * 24 }); return wasSet === " OK "; } Vollbildmodus öffnen Vollbildmodus beenden Einfaches Backoff mit Jitter:
function retryDelay ( attempt ) { const base = Math. min ( 1000 * 2 ** attempt, 30000 ); const jitter = Math. floor ( Math. random () * 500 ); return base + jitter; } Vollbildmodus öffnen Vollbildmodus beenden Dieser Stack ist weniger glamourös als Prompt Engineering.
Er ist aber auch der Grund, warum sich der Bot wie ein Produkt statt wie eine Hackathon-Demo verhalten hat.
Wenn dein Bot nur kurze Textantworten sendet und sonst fast nichts tut, kann ein synchroner Weg eine Weile gut funktionieren.
Nicht jedes Nebenprojekt braucht Kafka, Redis Streams, BullMQ, SQS oder einen kompletten Zuverlässigkeits-Kriegsraum.
Aber in dem Moment, in dem du eines davon hinzufügst, beginnt der einfache Weg, dich anzulügen:
Sobald der Webhook auf externe Systeme wartet, baust du keinen Chatbot mehr.
Du baust einen verteilten Workflow mit einem Chat-Frontend.
Wenn ich das Ganze neu aufbauen müsste, würde ich hier anfangen:
WhatsApp-Webhook -> verifizieren + parsen -> Ereignis speichern -> schnell 200 zurückgeben -> Job in die Warteschlange stellen Queue-Worker -> Medien abrufen, falls nötig -> KI ausführen -> CRM/Logs schreiben -> Antwort senden Monitoring -> Graph-API-Send response + WhatsApp-Webhook-Status- und Fehlerereignisse Vollbildmodus öffnen Vollbildmodus beenden Diese Architektur ist nicht ausgefallen.
Sie entspricht der Art, wie Meta Nachrichten tatsächlich ausliefert. Sie berücksichtigt, dass das Abrufen von Medien ein zweiter Schritt ist. Sie übersteht langsame LLM-Aufrufe. Und sie hört auf, so zu tun, als bedeute eine erfolgreiche API-Antwort, dass der Nutzer die Antwort definitiv erhalten hat.
Sobald ich die KI-Arbeit aus dem Webhook-Pfad ausgelagert hatte, war der nächste Engpass die Kostenvorhersagbarkeit.
Das ist besonders wichtig, wenn dein Bot Folgendes tut:
Abrechnung pro Token wird schnell lästig, wenn du gleichzeitig an der Produktionszuverlässigkeit debuggst und auf die Nutzungsähnliche Zähler schaust.
Das ist ein Grund, warum ich mag, was Standard Compute macht.
Es bietet dir einen OpenAI-kompatiblen API-Endpunkt, aber mit flatrate-monatlicher Preisgestaltung statt Abrechnung pro Token. Wenn du Bots oder Automatisierungen baust, die im Hintergrund GPT-5.4, Claude Opus 4.6 oder Grok 4.20 aufrufen, ist ein vorhersagbarer Kostenrahmen ein echter operativer Vorteil.
Besonders dann, wenn dein eigentliches Problem die Workflow-Zuverlässigkeit ist und nicht die Frage, ob ein zusätzlicher Retry 40 Cent gekostet hat.
Ich habe diesen Build besessen von Prompts begonnen.
Das fühlte sich etwa fünf Minuten lang enttäuschend an.
Und es stellte sich heraus, dass das viel nützlicher war als noch ein weiterer cleverer Prompt.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Posted on Oct 16 • Originally published at standardcompute.com
If your WhatsApp bot stops replying after the demo, the problem usually is not prompt quality.
Meta wants a verified public HTTPS endpoint. Replies have to respect the 24-hour service window. Media comes in as IDs, not files. And if you do slow AI work inside the webhook request, you’re setting yourself up for random silence.
Some got replies. Some got nothing. One person sent an image and got ghosted. Another got duplicate replies. Someone came back the next morning and the bot acted like the conversation didn’t exist.
My first instinct was the same one most AI builders have:
WhatsApp bots are not mainly an AI problem. They are a workflow reliability problem.
Once I accepted that, the architecture got a lot better.
A demo path for a WhatsApp bot is almost always too clean.
You send a short text. The webhook receives it. Your app calls the WhatsApp Cloud API. The bot replies. Everyone feels smart.
That flow hides the exact things that break in production:
They remove every hard part, then convince you the architecture is fine.
Right until users started sending images, PDFs, voice notes, and follow-up messages hours later.
Meta is strict about webhook setup, and honestly it should be.
For WhatsApp Cloud API, your webhook has to:
If verification is wrong, Meta does not sort of send webhooks.
The verification request looks like this:
GET <CALLBACK_URL>?hub.mode=subscribe&hub.challenge=<HUB_CHALLENGE>&hub.verify_token=<HUB_VERIFY_TOKEN> Enter fullscreen mode Exit fullscreen mode Your server has to return:
import express from " express "; const app = express (); app. use ( express. json ()); const VERIFY_TOKEN = process. env. WHATSAPP_VERIFY_TOKEN; app. get ( " /webhook ", ( req, res ) => { const mode = req. query [ " hub.mode " ]; const challenge = req. query [ " hub.challenge " ]; const token = req. query [ " hub.verify_token " ]; if ( mode === " subscribe " && token === VERIFY_TOKEN ) { return res. status ( 200 ). send ( challenge ); } return res. sendStatus ( 403 ); }); Enter fullscreen mode Exit fullscreen mode That part bit me once.
But the bigger mistake was what happened after verification.
I was doing too much work inside the webhook request itself.
A message would come in, then my flow would:
If your webhook waits on GPT-5, Claude, OCR, HubSpot, Airtable, or Google Sheets, you’ve turned a message receipt endpoint into a distributed systems bottleneck.
That’s why bots feel random in production.
WhatsApp webhook -> verify + parse -> persist event -> return 200 -> enqueue job Worker -> fetch media -> run AI -> write CRM/logs -> send reply Enter fullscreen mode Exit fullscreen mode If you want a dead-simple local version, even a database table plus a polling worker is better than blocking the webhook on LLM latency.
This one wasted a stupid amount of time.
I would send a reply through the Graph API and get a normal success response.
So I assumed the message was on its way.
With WhatsApp Cloud API, errors can show up:
If you only log the first one, your dashboard can look healthy while users are seeing nothing.
I had observability for requests I initiated. I did not have observability for delivery events Meta sent back later.
Once I treated outbound messaging as an event stream instead of a single API call, debugging got much easier.
Raw WhatsApp payloads are consistent, but they’re not fun to route directly into app logic.
You usually care about fields buried in a structure like:
Instead of wiring that raw shape directly into n8n, Make, or custom handlers, I normalize it first.
function normalizeWhatsAppMessage ( payload ) { const change = payload. entry?.[ 0 ]?. changes?.[ 0 ]?. value; const msg = change?. messages?.[ 0 ]; if (! msg ) return null; return { provider: " whatsapp ", messageId: msg. id, from: msg. from, timestamp: Number ( msg. timestamp ), type: msg. type, text: msg. text?. body?? null, imageId: msg. image?. id?? null, audioId: msg. audio?. id?? null, documentId: msg. document?. id?? null }; } Enter fullscreen mode Exit fullscreen mode That one layer made retries, deduping, and media handling much less chaotic.
Text messages flatter your architecture.
The first time a user sends an image, PDF, or audio clip, your “simple chatbot” becomes a real integration project.
Incoming media webhooks do not include the file.
A lot of demos skip it because they only test text.
async function fetchMediaUrl ( mediaId, accessToken ) { const res = await fetch ( `https://graph.facebook.com/v23.0/ ${ mediaId } `, { headers: { Authorization: `Bearer ${ accessToken } ` } }); if (! res. ok ) { throw new Error ( `Failed to fetch media metadata: ${ res. status } ` ); } return res. json (); } Enter fullscreen mode Exit fullscreen mode And yes, there’s a timer here too.
If you delay media retrieval, your “supports images” feature gets fragile fast.
I had one flow where image analysis sat behind lower-priority jobs. It looked elegant on paper. In practice, it was a reliability bug.
fetch media immediately, store what you need, then do slower AI work later.
This is where people say, “WhatsApp randomly stopped replying.”
Your message policy changed underneath you.
WhatsApp has a 24-hour customer service window.
It starts when a user messages or calls you, and each new user message resets the timer.
Inside that window, you can send free-form replies. Outside it, you need an approved template.
So yes, your bot can look perfect in testing and then “break” in production once real conversations age out.
That’s not an edge case. That is runtime behavior.
Once I started treating policy rules as part of the system design, the bot got much easier to reason about.
Meta documents throttling errors like 130429 for rate limits.
The important part is that you should handle errors based on the code and details fields, not just HTTP status.
A dumb retry loop can turn one temporary issue into three new ones:
One inbound event got processed twice. My worker retried an outbound send too aggressively. The user got duplicate messages. My logs looked like a conspiracy board.
async function shouldProcessMessage ( redis, messageId ) { const key = `wa:msg: ${ messageId } `; const wasSet = await redis. set ( key, " 1 ", { NX: true, EX: 60 * 60 * 24 }); return wasSet === " OK "; } Enter fullscreen mode Exit fullscreen mode Basic backoff with jitter:
function retryDelay ( attempt ) { const base = Math. min ( 1000 * 2 ** attempt, 30000 ); const jitter = Math. floor ( Math. random () * 500 ); return base + jitter; } Enter fullscreen mode Exit fullscreen mode That stack is less glamorous than prompt engineering.
It is also the reason the bot started behaving like a product instead of a hackathon demo.
If your bot only sends short text replies and does almost nothing else, a synchronous path can be fine for a while.
Not every side project needs Kafka, Redis Streams, BullMQ, SQS, or a whole reliability war room.
But the moment you add any of these, the simple path starts lying to you:
Once the webhook is waiting on external systems, you are not building a chatbot anymore.
You are building a distributed workflow with a chat frontend.
If I had to rebuild the whole thing, I’d start here:
WhatsApp webhook -> verify + parse -> store event -> return 200 fast -> queue job Queue worker -> fetch media if needed -> run AI -> write CRM/logs -> send reply Monitor -> Graph API send response + WhatsApp webhook status/error events Enter fullscreen mode Exit fullscreen mode That architecture is not fancy.
It matches how Meta actually delivers messages. It handles the fact that media retrieval is a second step. It survives slow LLM calls. And it stops pretending a successful API response means the user definitely got the reply.
Once I moved AI work off the webhook path, the next bottleneck was cost predictability.
That matters a lot if your bot is doing:
Per-token billing gets annoying fast when you’re debugging production reliability and also watching usage meters.
That’s one reason I like what Standard Compute is doing.
It gives you an OpenAI-compatible API endpoint, but with flat monthly pricing instead of per-token billing. If you’re building bots or automations that call GPT-5.4, Claude Opus 4.6, or Grok 4.20 behind the scenes, predictable cost is a real operational advantage.
Especially when your actual problem is workflow reliability, not whether one extra retry cost 40 cents.
I started this build obsessed with prompts.
That felt disappointing for about five minutes.
And that turned out to be much more useful than one more clever prompt.