
WhatsApp Cloud API Agent Architecture: Webhooks and Queues
How a message moves through webhook, queue, agent and reply on the WhatsApp Cloud API, with handling for duplicates, ordering and failures in production.
Want to apply this in your company?
Book a call with Juan →WhatsApp Cloud API Agent Architecture: Webhooks and Queues
A WhatsApp Cloud API agent architecture separates four stages: a webhook endpoint that validates and responds fast, a queue that holds the work, a worker that runs the agent per conversation, and a send step that records what was replied. Duplicates are solved by using the message ID as a unique key. Ordering is solved by processing one conversation at a time, without trusting arrival order. The rest of the design exists so that a failure at any stage does not produce a repeated reply, an out-of-context reply or a lost message.
Key takeaways
- The webhook only validates the signature, stores the event and enqueues it. No model call happens inside it.
- The incoming message ID is the deduplication key, written atomically before any processing starts.
- Ordering is guaranteed per conversation, not globally: a queue partitioned by customer number, with one worker at a time on each conversation.
- The agent reads history from your own storage, treats customer text as untrusted data, and acts only through tools with limited permissions.
- Every stage needs logging with a correlation ID, so you can later answer what arrived, what was decided and what was sent.
What path does a message take from webhook to reply?
The path has seven steps, and each one should be an isolated point of failure:
- Meta sends a POST to your endpoint with the event (an incoming message or a status update).
- The endpoint validates the signature, stores the raw payload and returns success.
- A deduplication step discards events already seen and puts new ones on the queue.
- A worker picks up the event, respecting the order of the conversation.
- The worker builds the context, calls the agent and receives a decision: reply, run an action or hand off to a human.
- Guardrails validate the output and the action before any external effect.
- The send step calls the Meta API, records the result and marks the event as complete.
The reason for so much separation is simple. Each stage has a different response time and a different failure mode. The webhook has to be fast. The model can be slow or fail. A send can be refused because of a channel rule. If everything happens in the same request, any slowness turns into Meta resending the event, and any error turns into a lost message.
If you are still choosing between channels and approaches, read WhatsApp Business API vs App for an AI Agent and the guide to integrating AI into your company's WhatsApp first.
What should the webhook endpoint do, and what should it not do?
According to Meta's documentation, the webhook has two behaviors. At setup, Meta sends a GET request with a verification token and a challenge, and your endpoint returns the challenge if the token matches. In operation, Meta sends POST requests with the event and a signature header calculated with your app secret. Confirm the exact field and header names in the official, current documentation before you implement, because they are part of your contract with Meta.
What the endpoint should do, in this order:
- Calculate the signature over the raw request body, before any parsing, and reject the request if it does not match. Re-serializing the JSON before validating breaks the signature.
- Store the raw payload in durable storage, with the time it was received.
- Enqueue a reference to the event, not the full text, to keep the queue light.
- Respond with success as quickly as possible.
What it should not do: call the model, query the CRM, download media or send a reply. Meta resends events when it does not receive success, and that is correct from its point of view. If your endpoint is slow because it is waiting on a model, you are manufacturing duplicates yourself.
The same principle appears with other providers. Stripe's webhook documentation is a good reference for how to think about signature verification, fast responses and asynchronous processing, even though the WhatsApp details differ.
How do you handle duplicate messages?
Duplicates are not a rare defect. They are expected behavior of any delivery system that retries. They happen because Meta resends, because your own worker retries after a partial failure, and because someone reprocesses a queue by hand. The defense is idempotency: processing the same event twice must have the same effect as processing it once.
The incoming message ID (the id field inside the event) is the natural key. How you use it matters more than which technology you pick:
- Deduplicate with an atomic operation: insert the ID into a table with a uniqueness constraint, or use a conditional write command in a cache. Checking with a read and writing afterward opens a race window between two simultaneous events.
- Deduplicate at queue entry and protect again in the worker. A single barrier does not cover reprocessing.
- Keep the event state: received, processing, completed, failed. An event stuck in processing needs a deadline for being picked up again, or it stays stuck forever.
- Separate event types. Status updates (sent, delivered, read, failed) arrive through the same channel as messages, but they should not trigger the agent.
There is a second layer that is often forgotten: duplicate actions. If the agent creates an order, opens a ticket or books a time slot, a resent event must not create two. The solution is to derive an idempotency key from the combination of the message ID and the intended action, and pass it to the target system when it accepts one. Stripe's guide to idempotent requests shows this pattern in a real API. When the target system does not offer this feature, record the action on your side before executing it and check that record before repeating.
Sending the reply to the customer deserves the same care. Do not assume that calling the send API twice is harmless: the customer would receive two messages. Write the send intent first and the result afterward. If there is doubt (a timeout with no response), check your record and the status events received before trying again. This is an implementation practice, not a guarantee from Meta.
How do you preserve message order?
Do not treat webhook delivery as ordered. Two messages sent in sequence by a customer can arrive inverted, or the second can arrive while the first is still being processed. Event timestamps help, but they can tie, so use them as support and not as the only criterion.
The design that works is to order per conversation, not across the whole system:
- Use the customer number (or the conversation ID) as the queue partition key.
- Guarantee a single active worker per conversation, with a lock or with the partition itself.
- Keep a sequence number per conversation in your database, assigned when the event is accepted.
- Before sending the reply, check whether anything new arrived in that conversation after processing began. If it did, discard the finished reply and generate another with the full context.
That last check prevents a classic error: the agent answers the first question while the customer has already corrected the information in the third message.
A complementary technique is to batch short messages. Customers write in bursts of loose sentences. Waiting for a short window of silence and handing everything to the agent as a single turn reduces model calls and improves the answer. The window size is a product decision you tune during the pilot, not a universal value.
How does the agent fit in, and what can it do?
The worker does not pass Meta's raw payload to the model. It builds the context from your own storage: recent conversation history, customer data the flow is allowed to use, current state (for example, whether a human agent has taken over) and the agent's instructions. If a human is handling the conversation, the agent does not reply. This state must be read on every turn, and the handoff design is covered in WhatsApp AI Agent Human Handoff: A Production Playbook.
Three safety rules apply to this step:
- Customer text is untrusted input. It can contain disguised instructions. The OWASP guidance on preventing prompt injection describes the defenses, and the main one is to never give the agent more permission than the flow needs.
- Tools are few, named and narrowly scoped. Read access first, write access only when necessary, and sensitive actions with human approval.
- Output and action pass through validation before they take effect. The patterns are in Guardrails for AI Agents: Input, Output and Action Controls.
Media (audio, image, document) enters through a separate queue. The event carries a media ID, and the download and transcription happen in another worker, which then returns the result to the conversation while respecting the same ordering. Mixing this into the main flow makes one audio message block every message after it.
How do you handle failures, resends and observability?
Failures happen in three places: in the model, in the tools and in the send. For each one, decide in advance what happens:
- Retry with growing wait times for temporary failures, with a limit on attempts.
- A dead-letter queue for whatever exceeds the limit, with an alert to a person rather than silent discarding.
- A fallback reply, short and honest, for when the agent cannot answer, combined with a handoff.
- Explicit handling of Meta send refusals, such as messages outside the customer service window, which are not solved by repeating the call.
To operate this, every event gets a correlation ID that follows the webhook, the queue, the model call, the tools and the send. With that, the question "why did the customer receive that reply?" has an answer. The OpenTelemetry observability primer explains the logic of traces, metrics and logs. In practice, track queue age, the time between message received and reply sent, the handoff rate and the volume in the dead-letter queue.
On personal data: the raw payload contains the phone number and the conversation content. Define how long it is kept, who can access it and what is masked in logs. Legal basis and record keeping are covered in LGPD Compliance for AI on WhatsApp: Legal Basis, Consent, Records, and the ANPD is the official reference for guidance.
Illustrative scenario: three messages in a row
Imagine a store that uses the agent to report order delivery times. The customer writes: "Hi", then "I wanted to ask about my order", and then "it's 4521, not 4512". This is an illustrative scenario, not a real case.
Without ordering and duplicate control, the system might answer about the wrong order, or answer twice, because Meta resent the first event while the model was still thinking. With the architecture described here, the flow changes:
- Each event is stored and deduplicated by its ID, so the resend is ignored.
- The three messages land in the same partition and wait for a short window of silence.
- The worker hands all three to the agent as a single turn.
- If the third message arrives during processing, the finished reply is discarded and redone.
- The order lookup uses a read-only tool, and the send step records its intent before calling the API.
The result is a single reply, about the right order, with a complete trail for audit.
How we apply this: map, limit, pilot, hand over
The method has four steps. First, map the current flow: which messages arrive, at roughly what volume, which questions repeat and who answers today. Second, limit data and actions: what the agent reads, what it can execute and what requires approval. Third, pilot a single real flow, with logs, the queue and deduplication already active, and a success metric agreed in advance (see How to Measure an AI Automation Pilot). Finally, hand over the operation with documentation and decide whether the pilot expands, adjusts or stops. This section describes a working method, not a client result.
Minimum checklist before switching on the pilot:
- The webhook signature is validated over the raw body.
- The endpoint responds without calling the model.
- The message ID has a uniqueness constraint.
- The queue is partitioned by conversation.
- There is a check for new messages before sending.
- A dead-letter queue exists with an alert.
- The handoff state is read on every turn.
- The retention period for the raw payload is defined.
Frequently asked questions
Do I really need a queue for a small WhatsApp agent?
For very low volume and a simple flow, you can start with an events table and a worker that reads from it, which is already a queue in its most basic form. What you should not do is call the model inside the webhook, because that causes resends and lost messages. Choose the queue tool based on your infrastructure and what your team can operate.
Does Meta guarantee that each message arrives exactly once and in order?
Do not treat delivery as guaranteed on either point. Resends happen when the endpoint does not confirm with success, and arrival order can differ from send order. That is why deduplication by ID and per-conversation ordering are the job of your architecture. Always confirm current behavior in Meta's official documentation.
How do I stop the agent from replying twice to the same customer?
Combine three things: atomic deduplication of the incoming event, recording the send intent before calling the API, and a single worker per conversation. Before sending, check whether a reply is already recorded for that turn. If the send times out, check the record and the statuses before repeating the call.
What should I do with messages the agent cannot process?
After a limited number of attempts with growing wait times, move the event to a dead-letter queue and alert a person. To the customer, send a short fallback reply and hand the conversation to a human agent. Discarding silently is the worst option, because the customer gets no answer and the team gets no signal that something is wrong.