All articles
    WhatsApp Business API

    WhatsApp Business API Architecture Explained

    A production WhatsApp Business API architecture has seven parts: Meta Cloud API, a thin webhook receiver, a durable queue, idempotent workers, a conversation store, an agent inbox, and a template manager. The receiver only verifies and enqueues. Everything slow, ordered or tenant-aware happens behind the queue, never inside the webhook request.

    Syed Husnain Haider Bukhari
    9 min read

    Most WhatsApp builds fail in the same place. Someone wires Meta's webhook straight into the business logic, an LLM call takes four seconds, the endpoint times out, Meta retries, and the customer receives the same reply three times. The architecture below exists to make that failure structurally impossible rather than carefully avoided.

    I built AI Walay, a multi-tenant WhatsApp Business SaaS where each customer connects their own Cloud API credentials and runs campaigns, contacts and replies from one dashboard. This is the shape that survived contact with real traffic, described component by component.

    What is the WhatsApp Business API architecture?

    The WhatsApp Business API architecture is a message pipeline, not a request-response app. Meta hosts the transport, you host everything that reacts to it: a webhook receiver that acknowledges instantly, a queue that absorbs bursts, workers that do the real work, and stores that hold conversations and template state.

    With the Cloud API, Meta runs the messaging infrastructure and exposes it through the Graph API. You POST to an endpoint scoped to a business phone number ID, and Meta pushes inbound messages and delivery statuses to a webhook URL you own. Everything upstream of that URL is Meta's problem. Everything downstream is yours.

    The seven components, what each one owns, and what breaks when it is missing.

    ComponentResponsibilityWhat breaks without it
    Meta Cloud APIHosted send and receive on the Graph API, scoped to a phone number IDThere is no supported alternative transport; Meta sunset the self-hosted On-Premises client in October 2025
    Webhook receiverVerify the signature, persist the raw payload, enqueue, return 200Timeouts trigger Meta retries and duplicate processing
    Durable queueAbsorb bursts, partition by conversation, retry with backoffA slow model call or database write silently loses inbound messages
    Worker poolDeduplicate, persist, run the bot, call the send endpointBusiness logic runs inside the HTTP request and cascades on failure
    Conversation storeMessages, contacts, delivery statuses, session window stateNo context for the model, no audit trail, no billing reconciliation
    Agent inboxHuman view, assignment, bot-to-human handoffEvery case the bot cannot close becomes a lost customer
    Template managerTemplate versions, categories and approval state per tenantCampaigns fail at send time with opaque API errors

    How does a message actually flow through the system?

    Inbound and outbound are two different paths that meet in the conversation store. Inbound is push, driven by Meta's webhook. Outbound is pull, driven by your worker calling the messages endpoint and then waiting for status callbacks on the same webhook.

    The full inbound path, in order:

    1. 1A customer sends a message. Meta accepts it and POSTs a JSON payload to your webhook URL, shaped as object, entry, changes, value.
    2. 2The receiver validates the X-Hub-Signature-256 header against your app secret. An unsigned request is dropped, not processed.
    3. 3The receiver writes the raw payload to an append-only events table and pushes a job onto the queue, keyed by business phone number ID plus contact WA ID.
    4. 4The receiver returns HTTP 200. Target is under 200 milliseconds, and nothing in that path may call an external API.
    5. 5A worker picks up the job, checks the message ID against the dedupe index, and exits early if it has seen it.
    6. 6The worker resolves the tenant from the phone number ID, loads conversation context, and decides: bot reply, route to a human, or both.
    7. 7If the bot replies, the worker POSTs to the messages endpoint with the tenant's own access token, stores the returned message ID, and marks the message as sent.
    8. 8Meta later delivers sent, delivered, read or failed status callbacks to the same webhook. Those flow through the identical path and update the stored message row.

    That last step surprises people. Status callbacks are far more numerous than inbound messages, especially during a campaign send, and they hit the same endpoint. If your receiver is doing anything expensive, a 5,000-contact campaign is the thing that takes it down. The full credential and webhook setup sequence is covered in how to set up the WhatsApp Cloud API.

    "The webhook endpoint is not where your product lives. It is a mailbox slot, and mailbox slots do not think."

    Why the webhook receiver should be the dumbest service you own

    The receiver has exactly one job: convert an HTTP request into a durable queue entry and get out. Every second it spends doing something else is a second closer to a retry, and retries are where duplicate replies come from.

    from fastapi import FastAPI, Request, Response
    
    app = FastAPI()
    
    @app.post('/webhooks/whatsapp')
    async def receive(request: Request) -> Response:
        body = await request.body()
        if not valid_signature(body, request.headers.get('X-Hub-Signature-256')):
            return Response(status_code=403)
    
        # Persist raw, enqueue, acknowledge. Nothing else belongs in this function.
        event_id = await events.insert_raw(body)
        await queue.enqueue('whatsapp.inbound', {'event_id': event_id})
        return Response(status_code=200)

    Things that do not belong inside the webhook handler, no matter how fast they look in development:

    • Any call to OpenAI or Anthropic Claude. Model latency is variable and unbounded from your side.
    • Any outbound call back to the Cloud API send endpoint.
    • Vector search, embedding generation, or a retrieval hop against pgvector.
    • CRM writes to GoHighLevel, HubSpot or anything else with its own rate limits.
    • Anything that can throw. A 500 from your handler is an instruction to Meta to send the payload again.

    How do you handle webhook retries and duplicates?

    Deduplicate on Meta's message ID with a unique database constraint, and treat every handler as replayable. Meta's Cloud API webhook documentation states that when an endpoint returns anything other than HTTP 200, delivery is retried with decreasing frequency for up to seven days, and warns that these retries can produce duplicate notifications.

    Seven days is the number that should change your design. A payload can arrive days after the event, long after the conversation moved on. Your worker has to be able to process a stale duplicate without emitting a second reply, without double-charging a campaign, and without corrupting conversation state.

    The idempotency rules I apply on every WhatsApp build:

    • Unique index on the message ID (the wamid). Insert first, and let the constraint violation be your dedupe check.
    • Status callbacks update by message ID and only move forward through sent, delivered, read. A late sent callback never overwrites a stored read.
    • Outbound sends carry an internal idempotency key so a retried job cannot produce two WhatsApp messages.
    • Store the raw payload before processing, so a bug can be replayed against fixed code rather than lost.
    • Return 200 even for payloads you intend to ignore. Reserve non-200 for signature failures and genuine infrastructure faults.

    Does the WhatsApp Cloud API guarantee message ordering?

    No, and you should not build as if it does. Retries, parallel workers and independent status callbacks all reorder events. Ordering is something you reconstruct, not something you receive.

    The fix is cheap: partition the queue so that all jobs for one conversation key, meaning business phone number ID plus contact WA ID, run on a single consumer with one job in flight at a time. Global ordering is unnecessary and expensive. Per-conversation ordering is what users actually perceive, and it costs you one partition key.

    Then sort on Meta's own timestamp when rendering the thread, not on your insert time. A message that arrived through a three-hour retry belongs where it happened, not at the bottom of the inbox.

    What changes when the WhatsApp Business API architecture is multi-tenant?

    Multi-tenancy changes three things: credentials become data, rate limits become per-tenant, and every query needs a tenant predicate that cannot be forgotten. In AI Walay, tenants connect their own WhatsApp Business credentials, so the system holds many access tokens and routes purely on the phone number ID in the incoming payload.

    Single-tenant assumptions that break the moment you have a second customer.

    ConcernSingle-tenantMulti-tenant
    CredentialsAccess token in an environment variableEncrypted per-tenant token rows, rotated independently
    Webhook routingEvery payload belongs to youResolve tenant from phone number ID before any write
    Rate limitsOne global budgetPer business phone number pacing, plus a per-tenant queue quota
    TemplatesOne approved setApproval state tracked per WABA, per language, per category
    Data isolationApplication-level filtering is survivableRow-level security in Postgres or Supabase, enforced below the app
    Noisy neighboursNot a concernOne tenant's campaign must not starve another tenant's replies

    Rate limits deserve their own attention. Meta's Cloud API overview documents throughput limits per app and per WhatsApp Business Account, along with per-recipient pacing and a throttling error code. The exact figures move, so check the current docs, but the architectural consequence is fixed: you need a token bucket per business phone number in front of the send path, and a queue that can shed campaign load without touching inbound replies. I lay out this kind of queue-backed pipeline work in more depth on AI automation services.

    How should the template manager and session window work?

    Treat templates as versioned, tenant-scoped resources with a lifecycle, not as strings in your code. A template has a category, a language, an approval state, and variables, and any of those can change on Meta's side without your app knowing.

    The rule that drives the whole design is the customer service window. Free-form replies are only permitted inside an open window opened by the customer's own message. Outside it, business-initiated messages must use an approved template. That means your conversation store has to track window state per contact, and your send path has to branch on it before it constructs a payload.

    What the template manager needs to hold:

    • A local mirror of every template with its Meta approval status, refreshed on a schedule and on webhook status updates.
    • Category, because category drives both delivery rules and billing. The mechanics are covered in WhatsApp Business API pricing.
    • Variable schemas, so a campaign can be validated before send rather than failing per-recipient.
    • A per-tenant view, since one tenant's rejected template must never block another's send.

    What does the agent inbox need from the architecture?

    The inbox needs three things the backend must provide deliberately: real-time push of new messages, an explicit conversation state machine, and a hard handoff flag that silences the bot the moment a human takes over.

    The handoff flag is the piece teams forget. Without it, an agent types a careful reply and the bot answers over the top of them two seconds later. I model conversations with an owner field: bot, human, or unassigned. Only the owner may send, and any inbound message from a human agent flips ownership and starts a timer before the bot can reclaim it.

    Real-time delivery to the browser is best handled by whatever your database already offers rather than a second messaging system. Supabase realtime subscriptions on the messages table were enough for AI Walay, which kept the moving-part count down. If you want the conversational layer on top of this, see how to build an AI chatbot on WhatsApp, and for the retail patterns that sit on it, ecommerce automation builds.

    The failure modes worth designing for on day one

    Most incidents I have debugged inside a WhatsApp Business API architecture trace back to one of five causes. All of them are cheap to prevent in the design and expensive to fix in production.

    Symptoms, causes and the structural fix.

    SymptomRoot causeFix
    Customer gets the same reply repeatedlySlow webhook handler timing out, Meta retryingEnqueue and return 200; dedupe on message ID
    Replies appear out of orderParallel workers on one conversationPartition the queue by conversation key
    Campaign send fails halfwayRate limit or template state change mid-runToken bucket per phone number, revalidate templates before send
    Bot talks over a human agentNo ownership model on the conversationExplicit owner field plus reclaim timer
    Tenant sees another tenant's messagesFiltering done in application code onlyRow-level security enforced in the database

    None of this is exotic. It is ordinary queue-and-worker design applied to a transport with strong opinions about retries and templates. If you are deciding whether you even need the API rather than the app, start with WhatsApp Business API vs the WhatsApp Business App, and if you want the integration built rather than described, that is what I do on WhatsApp Business integrations.

    Key takeaways

    • A sound WhatsApp Business API architecture separates a fast webhook receiver from slow workers with a durable queue between them.
    • Meta retries undelivered webhooks for up to seven days, so idempotency keyed on the message ID is mandatory, not optional.
    • Ordering must be reconstructed per conversation by partitioning the queue; the transport gives you no guarantee.
    • Multi-tenancy turns credentials, rate limits and template approval state into per-tenant data enforced below the application layer.
    • Templates and the customer service window drive the send path, so conversation state must track window status per contact.
    • An explicit conversation ownership model is the difference between a usable agent inbox and a bot that interrupts your staff.

    Frequently asked questions

    How much does it cost to run a WhatsApp Business API architecture?
    Two separate costs. Meta bills messaging through its own conversation and per-message pricing, which varies by template category and country, so check Meta's current pricing documentation. Your infrastructure cost is a small web service, a queue, a Postgres database and worker capacity, which is modest until campaign volume forces you to scale workers horizontally.
    Can I use serverless functions for the WhatsApp webhook receiver?
    Yes, and it is a good fit, because the receiver is stateless and short-lived. The constraint is cold starts: a function that takes two seconds to wake up will occasionally time out and trigger a Meta retry. Keep the function warm, keep it under a few hundred milliseconds, and put the real work behind a queue.
    What is the difference between the WhatsApp Cloud API and the On-Premises API?
    Hosting, and only one of them still works. The Cloud API is Meta-hosted and integrated over the Graph API. The On-Premises API ran in containers you operated, but Meta sunset it: the final client version expired in October 2025 and it can no longer send messages. Build on the Cloud API and treat On-Premises material you find as historical.
    Do I need a queue if I only handle a few messages a day?
    Yes, if any part of your reply path calls a model or an external API. Volume is not the reason for the queue; latency and retry semantics are. A single message that triggers a slow LLM call can still time out the webhook and produce a duplicate reply. A queue is a few lines of code either way.
    Can one webhook endpoint serve multiple tenants?
    Yes. Every inbound payload carries the business phone number ID in the metadata, and that is your tenant key. One endpoint, one signature check, then resolve the tenant before any read or write. The alternative, a subdomain or path per tenant, adds deployment work without adding isolation you cannot get from the payload.
    Is it worth building multi-tenant from day one?
    It is worth it if you plan to resell the same system. Adding tenant scoping to a schema with a handful of tables is a day of work. Retrofitting it after real customer data exists means migrating every table, rewriting every query and testing for cross-tenant leakage, which is where the genuinely dangerous bugs live.
    What happens if my server is down when a WhatsApp message arrives?
    Meta's webhook documentation says delivery is retried with decreasing frequency for up to seven days when the endpoint does not return HTTP 200. You will not lose the message, but it may arrive much later and possibly more than once, which is exactly why idempotent handlers and timestamp-based ordering matter.
    How do I stop a campaign send from delaying customer replies?
    Separate the queues. Campaign sends and inbound replies should never share a worker pool, because a 5,000-recipient batch will monopolise it. Give inbound its own consumers, put campaigns on a lower-priority queue behind a per-phone-number rate limiter, and let campaigns take longer rather than letting replies wait.

    Sources

    Tags:
    Meta Cloud APIWhatsApp Business APISystem DesignMulti-TenancyFastAPISupabase
    HB

    Written by Syed Husnain Haider Bukhari

    AI engineer, data scientist, and founder of Revolutionary Technologies LLC. Ships production AI agents, automations, and data platforms for teams in the US, UK, and UAE — including AgentFlow, AI Walay, and ProLeads.

    Get in touch →

    Related pages

    Want this built instead of researched?

    Book a 30-minute scoping call. You get a one-page plan and a fixed-scope quote within 48 hours.

    Start a conversation

    Let's Create a Revolution