WhatsApp Webhooks: Debouncing Photo Albums and Draining Queues

Pilgrims will not install an app, so UmrahFlow's intake runs on WhatsApp. Five patterns turn photo albums, duplicate messages and mixed chatter into clean database records.

By Wasif Rehman · · Automation

In short

  • Adapt to a standard wire format at the edge: with a Twilio-compatible relay, the dashboard would not change if the transport did.
  • Store every message before processing it, so webhooks stay fast and a crash loses nothing.
  • Debounce bursts into batches: one album should mean one batch and one reply.
  • Classify before acting, and let human review make classification mistakes cheap.
  • Trace every agent turn, because you cannot debug what you didn't record.

Why build an app intake on WhatsApp

If your users are Umrah pilgrims, the interface question answers itself: they are already on WhatsApp, and they will not install your app. So the entire intake for UmrahFlow runs through WhatsApp.

That means turning the messiest input stream imaginable into clean database records:

  • Photos arrive as albums.
  • Messages arrive twice.
  • Questions arrive mixed in with documents.

Here is the machinery that copes with it.

WAHA and a Twilio-compatible WhatsApp webhook

The first choice was infrastructure.

Two ways to connect a WhatsApp number
OptionWhat it means for the agency
Official WhatsApp Business APIMeta's approval flows and usage-based pricing
WAHA (WhatsApp HTTP API)Self-hosted, runs in a container, speaks plain HTTP, and works with the existing number

For an agency that lives on its existing WhatsApp number, WAHA is the pragmatic self-hosted route.

In front of it sits a small relay that exposes a Twilio-compatible webhook format. That deliberate translation layer means the dashboard just implements a standard Twilio webhook. If the business ever moves to the official API through Twilio, the dashboard doesn't change at all.

  1. Pilgrim on WhatsApp
  2. WAHA container
  3. Relay (Twilio format)
  4. Dashboard webhook
  5. Postgres
How a WhatsApp message reaches the database

Store first, process later: queueing webhook messages

The webhook handler does almost nothing. It writes the message to Postgres and returns 200. All the real work happens in a separate agent service that polls for messages with processedAt = NULL on bot-enabled conversations.

This store-first pattern is what makes WhatsApp's chaos manageable:

  • Webhooks never time out, no matter how slow OCR is.
  • A crashed agent resumes exactly where it left off, because unprocessed rows are still unprocessed.
  • Retried webhook deliveries can be deduplicated at write time, instead of causing double replies.

How to debounce WhatsApp photo albums

Here is the subtle bug in any naive implementation. A pilgrim selects six passport photos and hits send. WhatsApp delivers six separate messages over several seconds.

Six photos sent as one album
ResultProcess each message on arrivalDebounced drain
OCRRuns six timesPhotos processed together
Passport batchesSixOne
Replies to the pilgrimSixOne

The fix is a debounce window. The drain loop only picks up a conversation once no new message has arrived for a quiet period, so photos that arrive together are processed together. It is a few lines of SQL filtering by timestamp, and it is the difference between "wow" and "what is this thing doing".

Classifying WhatsApp messages before OCR

Real conversations mix documents with chatter: greetings, questions, voucher photos, the occasional selfie. Before anything touches OCR, a classification step routes each drained batch.

Where each kind of message goes
InputRoute
Passport imagesPassport extraction (OCR)
Voucher imagesVoucher handling
TextA conversational reply

How passport images are read is covered in Passport OCR with AI, and why the review stage exists in When not to let the AI finish the job.

Debugging a WhatsApp chatbot with agent traces

A web app shows you what the user saw. A chat pipeline doesn't. So every agent turn writes an AgentTrace that records:

  • What was drained
  • How it was classified
  • What OCR returned
  • What reply was sent

When an agency staffer asks "why did the bot say this?", the answer is a database query, not archaeology.

The rest of the pipeline, from these records to a staged visa job, is in From WhatsApp photo to government portal.

Frequently asked questions

What is WAHA?

WAHA (WhatsApp HTTP API) is a self-hosted way to connect a WhatsApp number to your software. It runs in a container and speaks plain HTTP, which suited an agency that wanted to keep using its existing number.

How do you handle WhatsApp photo albums in a bot?

Debounce them. WhatsApp delivers an album as separate messages over several seconds, so the drain loop only picks up a conversation once no new message has arrived for a quiet period. The album becomes one batch and gets one reply.

Why store WhatsApp webhook messages before processing them?

The webhook stays fast no matter how slow OCR is, a crashed agent resumes where it left off because unprocessed rows are still unprocessed, and retried deliveries can be deduplicated at write time.

Why make a WhatsApp relay Twilio-compatible?

The dashboard then implements a standard Twilio webhook. If the business ever moves to the official WhatsApp API through Twilio, the dashboard doesn't change at all.

How do you debug a WhatsApp chatbot?

Record every turn. In UmrahFlow each agent turn writes an AgentTrace with what was drained, how it was classified, what OCR returned and what reply was sent, so "why did the bot say this?" becomes a database query.

Case study: UmrahFlow

All posts