# WhatsApp Business Platform Architecture: Templates, Sessions, and Webhooks

> The 24 hour session window, three template categories, queue first webhooks, WhatsApp Flows and per message pricing on the WhatsApp Cloud API.

- Canonical: https://thearchitecturedeepdive.blog/articles/whatsapp-business-platform.html
- Series: The Architecture DeepDive, No. 019
- Author: Barnabas Waweru
- Published: 2026-08-17
- Tags: Payments
- Cite as: Barnabas Waweru, "WhatsApp Business Platform Architecture: Templates, Sessions, and Webhooks", The Architecture DeepDive, https://thearchitecturedeepdive.blog/articles/whatsapp-business-platform.html

---

Architecture DeepDive

WhatsApp Business Platform

Two billion people use WhatsApp every day. The Cloud API is not a chat socket. It is a constrained messaging protocol: session states, pre-approved templates, and per-message billing. If you skip those rules, messages fail silently or the account gets flagged.

By **Barnabas Waweru** · August 17, 2026 · ~15 min read · Networking

## The Reframe

WhatsApp is not an SMS replacement with a green icon. The Meta Cloud API is a **regulated messaging channel** with hard protocol rules: a 24-hour session window that gates every message type, a three-category template system that must pass editorial review before any message can be initiated, a quality score that gates your ability to scale, and per-message billing that changed in July 2025. Miss any of these and your messages either fail silently or your account gets flagged.

Core Abstraction

The WhatsApp Cloud API is an **asymmetric duplex channel**. You send outbound via REST (`POST /messages`). Inbound messages and delivery events arrive via HTTPS webhooks Meta pushes to you. What you can send outbound depends entirely on whether a user-initiated session is open, that asymmetry is the entire architecture constraint you're building around.

REST Send Layer

Every outbound message is a `POST` to `graph.facebook.com/v23.0/{PHONE_NUMBER_ID}/messages` with a Bearer system-user token.

24-Hour Session Window

A customer-service window opens when a user messages you. It lasts 24 hours. Inside it, any message type is allowed. Outside it, only **approved templates** may be sent.

Template System

Three categories (MARKETING, UTILITY, AUTHENTICATION), each reviewed by Meta before use. Only APPROVED templates can send. PENDING and REJECTED templates fail silently.

Webhook Inbound

Meta POSTs events to your HTTPS endpoint. You verify `X-Hub-Signature-256`, return 200 immediately, and process asynchronously. Synchronous handling causes retry floods.

WhatsApp Flows

Multi-screen native UI journeys (forms, booking, surveys) embedded directly in the chat thread via declarative Flow JSON. No external browser or webview needed.

Per-Message Pricing

July 2025: old conversation pricing is retired. Every delivered template is billed by category. Utility templates inside an open service window are free.

## The Graph API Foundation

### Everything is the Graph API

WhatsApp Cloud API is not a standalone product, it is a specialized surface over `graph.facebook.com`. The same Graph versioning rules, error model, token taxonomy, and rate limit headers apply. Understanding the Graph once means understanding every Meta product. The WhatsApp-specific pieces are the phone number node, the WABA (WhatsApp Business Account) node, and the `/messages` edge.

// WhatsApp Cloud API, Object Hierarchy

```
Meta App (App ID + App Secret)
  │
  ├── WhatsApp Business Account (WABA node)
  │     ├── Phone Number 1  (PHONE_NUMBER_ID)
  │     │     └── POST /messages   → outbound send
  │     ├── Phone Number 2
  │     ├── Message Templates       (per WABA, per language)
  │     └── Webhook Subscription    (WABA-level events)
  │
  ├── System User                   (production credential)
  │     └── System User Access Token  (long-lived, non-expiring)
  │
  └── App Dashboard
        ├── App Secret              (webhook signature key)
        └── App Access Token        (app-level ops)
```

### Token Taxonomy

Token type determines what you can do and for how long. The correct production credential is a **system user access token**, not a user access token, not a page token. Regular user tokens expire in ~24 hours, which is a silent footgun for production servers. System user tokens are long-lived, business-scoped, and can be configured to never expire.

| Token Type | Lifetime | Use |
| --- | --- | --- |
| User access token (short) | 1 to 2 hours | Client-side reads after login |
| User access token (long) | ~60 days | Server calls on behalf of a person |
| **System user token** | Configurable, non-expiring | ✓ Production server automation |
| Business integration token | Long | Multi-tenant SaaS (Tech Provider, Embedded Signup) |

API Versioning, Always Pin

Current stable Graph version: **v23.0**. Unversioned calls default to the oldest supported version, a silent footgun. Pin the version in code. Graph versions are supported for roughly two years; track the changelog to plan upgrades. All examples in this article use `https://graph.facebook.com/v23.0/...`.

## The 24-Hour Session Window

The session window is the central constraint of the entire platform. It is not a rate limit, it is a policy gate that determines which message types you are allowed to send. Every architectural decision flows from it.

// Session Window State Machine

```
  USER SENDS MESSAGE
         │
         ▼
  ┌──────────────────────────────────────────────┐
  │   Customer-Service Window OPEN               │
  │   Duration: 24 hours from last user message  │
  │                                              │
  │   ✓ text, image, video, audio, document      │
  │   ✓ interactive (buttons, lists)             │
  │   ✓ location, reaction, contacts             │
  │   ✓ UTILITY templates (free)                 │
  │   ✓ MARKETING templates (billed)             │
  └────────────────────┬─────────────────────────┘
                       │ 24 hours pass
                       ▼
  ┌──────────────────────────────────────────────┐
  │   Window CLOSED                              │
  │                                              │
  │   ✗ free-form text, silently dropped        │
  │   ✗ interactive messages, silently dropped  │
  │                                              │
  │   ✓ APPROVED template messages ONLY          │
  │     (MARKETING, UTILITY billed, AUTH billed) │
  └──────────────────────────────────────────────┘

  EXCEPTION: Click-to-WhatsApp (CTWA) ads open a 72-hour window, not 24 h.
```

### Why This Shapes Everything

The session window means WhatsApp does not behave like email or SMS. You cannot cold-message users whenever you want. You must either:

-   **Wait for the user to message first**, then respond within 24 hours with any message type.
-   **Send an approved template** to initiate or continue outside the window, billed per message.
-   **Run a CTWA ad campaign**, earns a 72-hour free window per click, useful for marketing flows.

The practical implication: **your chatbot's free-form replies only work while a session is open**. Outside the window, the bot must initiate with a template or wait. Build your state machine around this from the start.

Silent Drop, The Hidden Failure Mode

Attempting to send a free-form message outside a session window, or sending a PENDING or REJECTED template, does **not return an error in the way you expect**. The API returns a 200 with a `wamid`, but the message is never delivered. You only discover this through the delivery status webhook (status: `failed`) or by watching the database and noticing messages that never reached `delivered`. Build explicit delivery tracking from day one.

## Template Architecture

Templates are the only message type that can cross session boundaries. They are pre-approved blueprints stored in your WABA, reviewed by Meta before use, and billed based on category and delivery. Getting the category right at submission time is critical, Meta re-categorizes templates and bills accordingly, regardless of what you submitted.

MARKETING

Promotions, offers, newsletters. Strictest review. Billed on every delivery, always, no exceptions.

Examples: flash sale alert, product launch, re-engagement

UTILITY

Transactional updates. Free inside an open service window; billed outside. The workhorse category for SaaS and e-commerce.

Examples: order shipped, appointment reminder, payment receipt

AUTHENTICATION

OTPs and verification codes. Dedicated OTP component with copy-code or one-tap autofill button. Volume tiers reduce unit cost. Billed per delivery.

Examples: login OTP, phone verification, MFA code

### Template Lifecycle

1

Create via API or WhatsApp Manager

`POST /v23.0/{WABA_ID}/message_templates` with `name`, `language`, `category`, and `components`. Include realistic `example` values, reviewers use them. Status becomes `PENDING`.

2

Meta Review

Automated and human review checks category accuracy, prohibited content, and opt-in requirements. Marketing templates face stricter scrutiny. Approval time varies from minutes to days. Poll `GET /{WABA_ID}/message_templates` or listen for the `message_template_status_update` webhook event.

3

APPROVED, Ready to Send

Status is `APPROVED`. Templates approved per language code, `en` and `en_US` are separate approvals. Send using `type: "template"` in the messages payload with component parameters substituted at send time.

4

Ongoing Quality Monitoring

Delivered templates accumulate quality signals (blocks, reports). Status can move to `PAUSED` (rate limited) or `DISABLED` if quality drops. A UTILITY template submitted with marketing content gets recategorized to MARKETING and billed accordingly.

### Template Payload

Parameters are substituted at send time into the approved template body. The API version is pinned in the URL. The `messaging_product` field is always `"whatsapp"`.

```
POST https://graph.facebook.com/v23.0/{PHONE_NUMBER_ID}/messages
Authorization: Bearer {SYSTEM_USER_TOKEN}
Content-Type: application/json

{
  "messaging_product": "whatsapp",
  "to": "254712345678",           // E.164, no leading +
  "type": "template",
  "template": {
    "name": "order_update",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Amani" },
          { "type": "text", "text": "#4821" },
          { "type": "text", "text": "out for delivery" }
        ]
      }
    ]
  }
}
```

Phone Number Format

The `to` field takes E.164 format **without a leading `+`**, e.g. `254712345678` for Kenya. Twilio's WhatsApp integration additionally requires the `whatsapp:+` prefix and a leading `+`. Daraja (M-Pesa) also uses the `254...` format but normalize explicitly per API, never reuse a string built for one system in another.

## Webhook Architecture

Every inbound message, every delivery event (sent, delivered, read, failed), and every template status change arrives via webhook. The webhook is your only source of truth for what actually happened. Getting the security and processing model right protects you from both injection attacks and retry cascades.

// Webhook Processing Architecture (Queue-First)

```
  META SERVERS
       │
       │  POST /webhook
       │  X-Hub-Signature-256: sha256=<hmac-hex>
       ▼
  ┌─────────────────────────────────────────────────┐
  │  1. Verify HMAC-SHA256 of RAW body bytes        │
  │     (constant-time comparison, timing attacks) │
  │                                                 │
  │  2. If invalid → 401, drop                      │
  │                                                 │
  │  3. If valid → enqueue payload → return 200     │
  │     (must return 200 before Meta's timeout)     │
  └───────────────────────┬─────────────────────────┘
                          │
              ┌───────────┴──────────────┐
              ▼                          ▼
        Message Queue              Event Queue
        (inbound msgs)             (status events)
              │                          │
              ▼                          ▼
        Async Worker               Async Worker
        (parse, respond,           (update delivery
         session check,             state, analytics,
         route to bot)              alert on failure)

  ⚠ NEVER: process synchronously, parse then re-stringify for signature,
           or store raw payloads without enqueuing first.
```

### Webhook Verification, Security Perimeter

Meta signs every POST with `X-Hub-Signature-256: sha256=<hex>`, HMAC-SHA256 of the **raw request body bytes**, keyed by your App Secret. Two critical rules: validate against the **raw body** (never `JSON.stringify(parsedBody)`, whitespace differences break it), and use a **constant-time comparison** to prevent timing attacks.

```
// TypeScript, Next.js App Router route handler (simplified)
import { createHmac, timingSafeEqual } from "crypto";

export async function POST(request: Request) {
  const rawBody = await request.arrayBuffer();
  const rawBytes = Buffer.from(rawBody);

  const sig = request.headers.get("x-hub-signature-256") ?? "";
  const expected = "sha256=" + createHmac("sha256", process.env.WHATSAPP_APP_SECRET!)
    .update(rawBytes)
    .digest("hex");

  // Constant-time comparison prevents timing attacks
  const valid =
    sig.length === expected.length &&
    timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

  if (!valid) return new Response("Unauthorized", { status: 401 });

  // Enqueue immediately, process asynchronously
  await queue.add("whatsapp-event", JSON.parse(rawBytes.toString("utf-8")));

  return new Response("OK", { status: 200 }); // Must return before Meta's timeout
}
```

### Webhook Event Structure

Every webhook POST wraps events in an `entry` array. Inbound messages and delivery statuses can arrive in the same payload, always handle both paths.

```
// Inbound message event
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "{WABA_ID}",
    "changes": [{
      "field": "messages",
      "value": {
        "metadata": { "phone_number_id": "..." },
        "messages": [{
          "id": "wamid.xxx",        // Use this for reactions/read receipts
          "from": "254712345678",
          "type": "text",
          "text": { "body": "Habari!" },
          "timestamp": "1723890000"
        }],
        "statuses": [{              // Delivery status for your outbound messages
          "id": "wamid.yyy",
          "status": "delivered",    // sent | delivered | read | failed
          "recipient_id": "254712345678",
          "timestamp": "1723890010"
        }]
      }
    }]
  }]
}
```

Rate Limits, Monitor X-App-Usage

Graph API responses include an `X-App-Usage` header, a JSON object with `call_count`, `total_time`, and `total_cputime` expressed as percentages. Throttle proactively before any value hits 100. The API returns error code `4` for app-level throttling and code `17` for user-level. Implement exponential backoff on these codes.

## WhatsApp Flows, Native UI in Chat

WhatsApp Flows is a declarative UI framework that renders multi-screen, native UI journeys inside the chat thread. No external browser, no webview. Users fill forms, book appointments, and complete lead-capture sequences without leaving WhatsApp. From an architecture standpoint, Flows turns your webhook endpoint into a UI backend.

### Two Build Modes

| Mode | How it works | When to use |
| --- | --- | --- |
| **Static Flow** | Pure Flow JSON, no server. All screen logic is declared in the JSON. Collected data arrives at your webhook when the user submits. | Lead capture, surveys, simple forms. Fast to build, no data endpoint to secure. |
| **Dynamic Flow** | Your encrypted data endpoint receives `data_exchange` calls between screens. You validate input, fetch live data, and route the user. All traffic is end-to-end encrypted with your WABA key pair. | Booking with live availability, conditional routing, real-time validation, personalized flows. |

// Dynamic Flow, Encrypted Data Endpoint

```
  WhatsApp App (on user's phone)
       │
       │  screen transition / submit
       ▼
  WhatsApp Infrastructure
       │  encrypts request with your WABA public key
       │
       │  POST /your-flow-endpoint
       │  (encrypted body)
       ▼
  Your Server
       │  1. Decrypt with WABA private key
       │  2. Parse action (screen_0_field, navigation_screen, ping)
       │  3. Apply business logic (validate, fetch live data)
       │  4. Encrypt response with same key pair
       │  5. Return encrypted response
       ▼
  WhatsApp Infrastructure → renders next screen on user's device
```

Flows vs Interactive Messages

Interactive messages (reply buttons, list menus) work inside a session window and cover simple one-step interactions, up to 3 buttons or 10 list rows. **Flows handle multi-step, multi-screen journeys** that go beyond what button/list interactions can express. They require more setup (Flow JSON authoring, key pair, optional data endpoint) but dramatically increase what you can accomplish inside the chat thread without redirecting to a browser.

## Cost Reality Check

Pricing Model Changed July 2025

Meta retired conversation-based pricing in July 2025. Every delivered template message is now billed individually by category. There is no free monthly conversation tier anymore. If your cost model was built on the old system, recalculate now.

### Per-Message Billing Model

| Category | Inside Window | Outside Window | Note |
| --- | --- | --- | --- |
| **MARKETING** | Billed | Billed | Always billed on delivery |
| **UTILITY** | Free | Billed | Best cost profile for transactional SaaS |
| **AUTHENTICATION** | Billed | Billed | Volume tiers reduce unit cost monthly |
| Service (free-form) | Free | Not allowed | Chatbot replies inside window cost nothing |

### Messaging Limits, The Portfolio-Wide Tier Ladder

As of October 2025, messaging limits are set per business portfolio (not per phone number) and cap unique users you can message outside a service window per rolling 24 hours.

250/day

New portfolio (unverified business), default starting tier

2,000/day

After business verification + algorithmic quality volume

10,000/day

Algorithmic scaling, quality score must stay Green

100K+/day

Further algorithmic scaling → unlimited

**Scaling is algorithmic, not purchasable.** You cannot buy a higher tier. Quality rating (Green / Yellow / Red) computed from blocks and reports determines whether you can scale up. A low quality score blocks upward movement even with sufficient volume. Practical levers: clean opt-in lists, message relevance, frequency discipline, easy opt-out in marketing templates.

Cost Optimization Strategy

Maximize free service messages by keeping the conversation open: respond to every user message quickly, encourage back-and-forth, and send UTILITY templates only when you genuinely need to restart a closed window. For Kenya (KE) deployments, verify the KE rate card separately, emerging market rates differ from US rates. And never misclassify marketing content as UTILITY, Meta re-categorizes and bills at the MARKETING rate.

## Platform Algorithms, Quality and Rate Limits

Quality Score

Green / Yellow / Red computed from recipient blocks and reports over recent messages. Since October 2025, low quality no longer auto-downgrades limits but permanently blocks upward scaling until resolved.

App-Level Rate Limits

Graph API calls per rolling hour computed from your app's active user engagement. Higher daily usage earns a higher ceiling. Monitor `X-App-Usage` (JSON: call\_count, total\_time, total\_cputime as %). Back off before any value hits 100.

Error Codes to Handle

`code 4`: app rate limit. `code 17`: user rate limit. `code 190`: invalid/expired token. `code 131026`\-series: message undeliverable (check specific subcode). Implement exponential backoff on 4 and 17; alert and rotate on 190.

Delivery Tracking

Track every `wamid` through its lifecycle: sent → delivered → read. Failed status with a subcode tells you why. Only delivered messages bill, undelivered (e.g. invalid number) are not charged.

## Key Takeaways

1

The 24-hour window is your primary architectural constraint

Design your session state machine around it from day one. Every chatbot response, every proactive outreach flow, and every integration with CRM or e-commerce systems must account for whether a user service window is open.

2

System user tokens are the production credential, no exceptions

Regular user access tokens expire in ~24 hours. A server running on a user token will silently fail overnight. Generate a system user in Meta Business Suite, assign it WABA permissions, and generate a non-expiring token before deploying.

3

Webhook architecture must be queue-first

Return 200 immediately after signature validation. Process asynchronously. Any synchronous processing that exceeds Meta's timeout triggers exponential-backoff retries that flood your endpoint. This is not optional at scale.

4

Validate webhook signatures against raw body bytes only

Never compute the HMAC against `JSON.stringify(parsedBody)`. Whitespace in the serialized JSON may differ from what Meta signed. Use a constant-time comparison (`timingSafeEqual`) to prevent timing attacks.

5

Category accuracy determines your billing

Meta re-categorizes misclassified templates and bills at the correct (typically higher) rate regardless of what you submitted. UTILITY is the right category for transactional messages and is free inside an open service window, the best cost profile for SaaS and e-commerce.

6

Scaling is earned, not purchased, quality is the gate

The messaging limit tier ladder (250 → 2,000 → 10,000 → 100,000 → unlimited) advances algorithmically based on quality signals from recipient behavior. A low quality rating blocks upward scaling permanently until resolved. Build clean opt-in, message relevance, and easy opt-out from the start.

// Official References

-   [WhatsApp Cloud API Overview](https://developers.facebook.com/docs/whatsapp/cloud-api), developers.facebook.com
-   [Send Messages Guide](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages), Cloud API v23.0
-   [Per-Message Pricing (July 2025+)](https://developers.facebook.com/docs/whatsapp/pricing), category billing model
-   [Messaging Limits](https://developers.facebook.com/documentation/business-messaging/whatsapp/messaging-limits), portfolio-wide tier ladder (October 2025+)
-   [Message Templates](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/), create, review, manage via API
-   [Access Tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens/), system user vs user access token
-   [Graph API Webhooks](https://developers.facebook.com/docs/graph-api/webhooks/getting-started), signature verification guide
-   [WhatsApp Flows](https://developers.facebook.com/docs/whatsapp/flows), static and dynamic mode documentation
-   [WhatsApp Business Platform Node.js SDK](https://whatsapp.github.io/WhatsApp-Nodejs-SDK/), official Meta SDK
