All articles Software Dev

Designing multi-tenant WhatsApp messaging

Sending one WhatsApp message is easy. Running the platform for fifty businesses at once is where the real design happens.

Sending a single WhatsApp message through the Cloud API takes about ten minutes and one curl command. Running the messaging platform for fifty different businesses at once — each with their own number, their own templates, their own customers — is a completely different problem. That second problem is where the actual engineering lives, and almost none of it is about sending messages.

One account per business

The unit you build around is the WhatsApp Business Account, or WABA. Every client business gets its own WABA, with one or more phone numbers attached. As the company connecting them all to WhatsApp, you operate as a Tech Provider sitting above many WABAs at once — you never share a number between clients, and you never mix one client's conversations into another's.

Getting this boundary right on day one matters more than anything else. Almost every later decision — how you store data, how you route events, how you rate-limit — comes back to "which WABA does this belong to?" If that answer is ever ambiguous, you have a data-isolation bug waiting to happen.

Onboarding without holding the keys

The temptation is to ask each client for their Meta login and set everything up yourself. Don't. The right path is Embedded Signup — Meta's hosted flow that lets a client connect their own WABA and number from inside your product. At the end of it you receive a token scoped to that client's account, granted through a system user, with a defined permission set for messaging and management.

This means you store a token, never a password. You can rotate it, you can revoke it per client, and a leak is contained to one account rather than your whole platform. The flow is fiddly to wire up the first time, but it is the difference between a credential vault you have to defend and a set of revocable, scoped tokens you can reason about.

One webhook, many tenants

Here is the part that surprises people: Meta sends every event for every client to a single webhook URL. Inbound messages, delivery receipts, template status changes, quality updates — all of it arrives at one endpoint. Your job is to fan it back out.

Each payload carries the WABA ID and the phone_number_id the event belongs to. That is your routing key. You verify the request signature, look up which tenant owns that number, and hand the event to the right place. Get this wrong and one client sees another client's messages. So the routing layer is small, boring, and the most security-critical code in the system — it deserves tests that assert isolation, not just "a message came through."

Templates and the 24-hour window

WhatsApp draws a hard line between replying to a customer and reaching out to one. Inside 24 hours of a customer's last message you can send freely. Outside that window you can only send pre-approved message templates, and every template is reviewed by Meta, tied to a specific WABA, and sorted into a category — marketing, utility, or authentication — that affects how it is priced and paced.

In a multi-tenant system this becomes a management surface of its own: clients want to create and edit templates, you want to validate them before submission, and you need to track approval status as it changes asynchronously via webhook. Treat templates as first-class objects in your data model, not as strings you paste into an API call.

Quality, limits, and noisy neighbours

Every number carries a quality rating, and that rating drives a messaging limit that scales up as a number behaves well and collapses if recipients start blocking or reporting it. One client running an aggressive campaign can damage their own number's standing — but if you have not isolated them properly, they can also exhaust shared resources and degrade everyone else.

The fix is per-tenant rate limiting on your side, sitting in front of Meta's limits. No single client should be able to saturate your outbound pipeline. This is the "noisy neighbour" problem every multi-tenant platform eventually meets, and WhatsApp is no exception.

The gotchas

  • Webhook deliveries retry on failure, so every handler has to be idempotent — process the same event twice and nothing should break or double-send.
  • Number registration needs a two-step PIN, and display names need separate approval; both block go-live in ways that are easy to forget when you are deep in code.
  • Quality and template-status changes arrive as webhooks, not as things you poll — build for events, not for cron jobs.
  • Map every inbound message to the right tenant and the right conversation thread; the second mapping is the one people skip.

What actually matters

None of the hard parts are about the message API. The hard parts are multi-tenancy: clean isolation between WABAs, a token lifecycle you can revoke and rotate, and one webhook that fans out to the right tenant every single time. Get those three right and adding the hundredth client costs nothing. Get them wrong and you find out at the worst possible moment.

Building something like this?

This is the kind of work we do day to day — integrations, messaging, and the infrastructure behind them. Tell us what you're working on.