Creative Codes
← All insights
AutomationJune 22, 20269 min read

WhatsApp Business API: Building Notification Bots and Transactional Flows

WhatsApp has 2B+ active users and higher open rates than email. Building reliable notification and transactional flows on the Business API requires understanding the template system, webhook delivery, and the 24-hour messaging window.

Muhammad Hassan

Founder, Creative Codes. 8 years on backends; last 3 deep on AI agents, RAG pipelines, and production scraping. Python, LangGraph, Playwright, n8n, FastAPI.

WhatsApp has a higher message open rate than email (98% vs ~20%), and in many markets — Middle East, Southeast Asia, Latin America — it's the primary business communication channel. But the Business API is not a simple REST endpoint. Getting reliable notifications and transactional flows working requires understanding the template approval system, the 24-hour conversation window, and the webhook delivery architecture.

API access: Cloud API vs On-Premise

Meta offers two ways to access the WhatsApp Business API:

Cloud API (recommended): hosted by Meta, accessed via REST endpoints at graph.facebook.com. No infrastructure to manage. Rate limits handled by Meta. This is the right choice for almost all production use cases.

On-Premise API: runs on your own servers. Required only for specific compliance scenarios where data must not leave your infrastructure. Significantly more operational overhead. Don't choose this unless you have a hard compliance requirement that the Cloud API cannot meet.

All examples in this post use the Cloud API.

Message types and the 24-hour window

WhatsApp has two messaging modes with different rules:

Template messages (business-initiated): pre-approved message templates sent to users. Can be sent at any time, regardless of whether the user has messaged first. Used for notifications, reminders, alerts, and transactional messages (order confirmations, shipping updates, OTPs).

Session messages (user-initiated): free-form messages sent within 24 hours of the user's last message. Once a user messages your number, you have a 24-hour window to send any content without a template.

The 24-hour window is the most common source of confusion. A bot that worked in testing (because the user had just sent a message) fails in production (because the user messaged 25 hours ago). Template messages are the only reliable mechanism for outbound notifications.

Template structure and approval

Templates must be submitted to Meta for approval before use. Approval typically takes a few minutes to a few hours.

A template has three parts:

  • Header: optional. Text, image, document, or video.
  • Body: the message text with optional variable placeholders ({{1}}, {{2}}, etc.).
  • Footer: optional static text.
  • Buttons: optional CTA or quick reply buttons.

Example order confirmation template:

text
Name: order_confirmation
Language: en_US
Category: TRANSACTIONAL

Body:
Your order {{1}} has been confirmed. Estimated delivery: {{2}}.
Track your order: {{3}}

Footer:
Reply STOP to unsubscribe

Variables are positional and filled at send time.

Sending a template message

python
import httpx

PHONE_NUMBER_ID = "your_phone_number_id"
ACCESS_TOKEN = "your_meta_access_token"

def send_order_confirmation(
    recipient_phone: str,  # E.164 format: +14155552671
    order_id: str,
    delivery_date: str,
    tracking_url: str,
) -> dict:
    url = f"https://graph.facebook.com/v20.0/{PHONE_NUMBER_ID}/messages"

    payload = {
        "messaging_product": "whatsapp",
        "to": recipient_phone,
        "type": "template",
        "template": {
            "name": "order_confirmation",
            "language": {"code": "en_US"},
            "components": [
                {
                    "type": "body",
                    "parameters": [
                        {"type": "text", "text": order_id},
                        {"type": "text", "text": delivery_date},
                        {"type": "text", "text": tracking_url},
                    ],
                }
            ],
        },
    }

    response = httpx.post(
        url,
        json=payload,
        headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
        timeout=10.0,
    )
    response.raise_for_status()
    return response.json()

The response includes a message ID (wamid) which you use to track delivery status via webhooks.

Receiving messages and delivery events via webhook

Meta pushes inbound messages and delivery status events to your webhook endpoint. Your webhook must respond with HTTP 200 within 5 seconds or Meta will retry.

python
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib

app = FastAPI()

WEBHOOK_VERIFY_TOKEN = "your_verify_token"
APP_SECRET = "your_app_secret"

@app.get("/webhook")
async def verify_webhook(request: Request):
    """Meta's webhook verification handshake."""
    params = dict(request.query_params)
    if (
        params.get("hub.mode") == "subscribe"
        and params.get("hub.verify_token") == WEBHOOK_VERIFY_TOKEN
    ):
        return int(params["hub.challenge"])
    raise HTTPException(status_code=403)

@app.post("/webhook")
async def receive_webhook(request: Request):
    """Receive messages and status updates."""
    # Verify the signature
    body = await request.body()
    signature = request.headers.get("X-Hub-Signature-256", "")
    expected = "sha256=" + hmac.new(
        APP_SECRET.encode(), body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=403, detail="Invalid signature")

    data = await request.json()

    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            value = change.get("value", {})

            # Inbound messages
            for message in value.get("messages", []):
                await handle_inbound_message(message)

            # Delivery status updates
            for status in value.get("statuses", []):
                await handle_status_update(status)

    return {"status": "ok"}

Status events include: sent, delivered, read, and failed. Log all of them with the wamid so you can track which messages were actually read.

Building a notification flow

For order updates across an e-commerce workflow:

text
Order placed
    → Send "order_confirmation" template
    → Log wamid to order record
    
Order shipped
    → Send "shipping_update" template with tracking number
    
Delivery attempted
    → Send "delivery_attempted" template with reschedule link

Delivery confirmed
    → Send "delivery_confirmed" template
    → (Optional) request review via quick reply buttons

Each step is triggered by an event from your order management system — a webhook, a database change, or a Celery task. The WhatsApp API call is one step in a larger automation, not a standalone system.

Rate limits and error handling

Meta enforces rate limits at the phone number level. For the Cloud API, the limit is 80 messages/second per phone number by default (expandable with Meta support). For most notification use cases, this is not a constraint.

Errors that require handling:

  • 131026 (message undeliverable): the recipient's phone is off or they've blocked your number. Mark as undeliverable, don't retry.
  • 131047 (re-engagement message blocked): you tried to send a session message outside the 24-hour window. Switch to a template message.
  • 131051 (message type not supported): the message type (e.g., interactive) isn't supported on the recipient's WhatsApp version. Fall back to a text template.
  • 130429 (rate limit hit): back off and retry after the retry_after duration in the error response.

Always retry on transient errors (5xx from Meta's infrastructure) with exponential backoff. Never retry on 131026 — a blocked or unreachable number won't become reachable on retry.

Interactive messages: buttons and list pickers

Beyond plain text templates, the Business API supports interactive message types that appear natively on the user's device:

Reply buttons (up to 3): quick response options the user taps rather than types. Useful for appointment confirmations ("Confirm / Reschedule / Cancel"), support triage ("Billing issue / Technical issue / Other"), and survey responses.

List messages: a menu with up to 10 items grouped into sections. The user taps a button to open the list and selects an item. Useful for order tracking options, FAQ categories, or product selection.

python
def send_appointment_confirmation(recipient_phone: str, appointment_details: str) -> dict:
    payload = {
        "messaging_product": "whatsapp",
        "to": recipient_phone,
        "type": "interactive",
        "interactive": {
            "type": "button",
            "body": {"text": f"Your appointment is confirmed: {appointment_details}. Please confirm your attendance."},
            "action": {
                "buttons": [
                    {"type": "reply", "reply": {"id": "confirm_yes", "title": "Confirm"}},
                    {"type": "reply", "reply": {"id": "confirm_reschedule", "title": "Reschedule"}},
                    {"type": "reply", "reply": {"id": "confirm_cancel", "title": "Cancel"}},
                ]
            },
        },
    }
    # ... send via API

Interactive messages can only be sent within the 24-hour session window — they cannot be sent as templates. For appointment reminders sent proactively, send a plain text template first; when the user replies, you're in a session window and can follow up with an interactive message.

Connecting WhatsApp to your automation stack

For n8n users, there's a native WhatsApp Business Cloud node that handles authentication and message sending without custom HTTP requests. For custom integrations, the Python pattern above is straightforward to wire into any automation stack.

The most common integration pattern: trigger from a CRM event (HubSpot deal stage change, Salesforce opportunity update, order status change in your e-commerce platform) → format the template variables → send via WhatsApp API → log the delivery status back to the CRM. This is covered in detail in the sales pipeline automation post.

What makes WhatsApp automation different from email

WhatsApp messages are delivered to a device people carry constantly and check immediately. The open rate difference vs email is real and consistent. But the constraints are also real: template approval adds friction, the 24-hour window limits free-form follow-up, and users will block your number if you send too many messages or irrelevant ones.

The right use cases for WhatsApp automation: transactional notifications (order status, appointment reminders, OTPs, delivery updates), high-priority alerts (account changes, security events), and conversational flows where the user has initiated contact. Marketing campaigns without explicit opt-in and high-frequency promotional messages are both against Meta's policies and likely to result in your number being blocked or your Business Account being suspended.

One practical operational note: monitor your quality rating in the WhatsApp Manager dashboard. Meta rates your number based on user feedback (blocks and reports). A rating drop from Green to Yellow is a warning. Red means your messaging limit has been reduced. Template message quality and opt-in hygiene are the two levers that keep your rating healthy.


If you're building WhatsApp notification flows or conversational bots and need the full architecture set up — webhook handling, template management, delivery tracking — tell us about the project.

Related: Building Production n8n Workflows: Architecture, Error Handling, Deployment | Automating the Sales Pipeline: Lead Capture, CRM Sync, and Follow-up Sequences

WhatsApp Automation services →

Related service

Need complex n8n workflows built to production standards?

AI Workflow Automation

We publish new posts every few weeks. See more on the insights page.