Guide 04

WhatsApp Cloud API bots: setup, webhooks, templates and the 24-hour window

WhatsApp is where customers are, which is exactly why it is strict. Use the official Cloud API, get opt-in, and design around the 24-hour window.

Guide3 min readUpdated

Official API only

There are two legitimate ways to automate WhatsApp: the Cloud API hosted by Meta (recommended) or a Business Solution Provider that wraps it. Libraries that automate the consumer WhatsApp app or WhatsApp Web violate the terms of service and get numbers banned. This guide covers the Cloud API.

Setup

  1. Create a Meta developer app of type Business and add the WhatsApp product.
  2. Meta gives you a test phone number and a temporary access token. You can message up to five verified recipient numbers while testing.
  3. For production, add your own phone number (it cannot be registered in the WhatsApp consumer app at the same time), verify your business, and set a display name that matches your brand.
  4. Create a System User in Business Manager, assign the app and the WhatsApp account, and generate a permanent token with whatsapp_business_messaging and whatsapp_business_management permissions. Temporary tokens expire within a day.

Keep the phone number ID (not the phone number itself) and the WhatsApp Business Account ID: API calls use them.

Sending a message

All calls go to the Graph API. Replace vXX.0 with the current Graph API version shown in the Meta docs.

curl -s "https://graph.facebook.com/vXX.0/$PHONE_NUMBER_ID/messages" \
  -H "Authorization: Bearer $WHATSAPP_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "to": "15551112222",
    "type": "text",
    "text": { "body": "Your order #1042 has shipped." }
  }'

Numbers use the international format without + or spaces. The response contains a message ID (wamid...) you can correlate with status webhooks.

The 24-hour customer service window

  • When a user messages you, a 24-hour window opens. Inside it you can send free-form messages: text, media, interactive buttons and lists.
  • Outside the window you can only send pre-approved templates. Templates belong to a category (marketing, utility or authentication), which determines review rules and price.
  • Template messages are charged per delivered message, by category and recipient country. Check Meta's current pricing page before you plan a campaign.
{
  "messaging_product": "whatsapp",
  "to": "15551112222",
  "type": "template",
  "template": {
    "name": "order_shipped",
    "language": { "code": "en_US" },
    "components": [{ "type": "body", "parameters": [{ "type": "text", "text": "#1042" }] }]
  }
}

Webhooks

Configure a callback URL and a verify token of your choosing in the app dashboard, then subscribe to the messages field.

Verification: Meta sends GET ?hub.mode=subscribe&hub.verify_token=...&hub.challenge=.... If the token matches, respond 200 with the challenge as plain text.

Notifications: Meta sends POST requests with an X-Hub-Signature-256: sha256=<hex> header, an HMAC-SHA256 of the raw body using your app secret. Verify it before parsing (try it in the webhook tester).

import hmac, hashlib, os
from fastapi import FastAPI, Request, Response, HTTPException

app = FastAPI()
APP_SECRET = os.environ["META_APP_SECRET"].encode()

@app.get("/whatsapp")
def verify(request: Request):
    q = request.query_params
    if q.get("hub.mode") == "subscribe" and q.get("hub.verify_token") == os.environ["WA_VERIFY_TOKEN"]:
        return Response(q.get("hub.challenge"), media_type="text/plain")
    raise HTTPException(403)

@app.post("/whatsapp")
async def receive(request: Request):
    raw = await request.body()
    expected = "sha256=" + hmac.new(APP_SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Hub-Signature-256", "")):
        raise HTTPException(401)
    payload = await request.json()
    for entry in payload.get("entry", []):
        for change in entry.get("changes", []):
            for msg in change["value"].get("messages", []):
                await enqueue(msg)          # dedupe on msg["id"], reply asynchronously
    return {"ok": True}

The same endpoint receives status updates (sent, delivered, read, failed) under value.statuses. Store them if you need delivery reporting.

Interactive messages

Inside the window you can send reply buttons (up to three) and list messages (up to ten rows), which are far more reliable than asking users to type exact keywords. Mark incoming messages as read and show a typing indicator to signal that the bot is working.

Opt-in, quality and limits

  • You must obtain opt-in before sending business-initiated messages, state your business name, and make opting out easy (honor "STOP" or a button).
  • Each number has a quality rating based on user blocks and reports. Low quality reduces how many unique users you can reach per day and can disable templates.
  • New numbers start with a limited number of business-initiated conversations per day; the tier rises automatically as you send high-quality messages.
  • Offer a path to a human. Bots that loop users endlessly generate blocks.

Read the WhatsApp Business Messaging Policy before launch: some industries and content are prohibited.

Check your understanding

1. What can you send outside the 24-hour window?
2. Which key signs WhatsApp webhook notifications?