WhatsApp Business API Integration Guide

Technology: whatsapp-business-api · Category: comms · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/whatsapp-business-api

Insight:

WhatsApp is Kenya's dominant chat app. The operational crux: you can only send free-form messages within a 24-hour window of the user's last message — outside it you need pre-approved template messages. Choose Meta Cloud API (cost/control) vs Twilio (speed/unification); same +254… E.164 trap as Africa's Talking.

██╗    ██╗██╗  ██╗ █████╗ ████████╗███████╗ █████╗ ██████╗ ██████╗
██║    ██║██║  ██║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗
██║ █╗ ██║███████║███████║   ██║   ███████╗███████║██████╔╝██████╔╝
██║███╗██║██╔══██║██╔══██║   ██║   ╚════██║██╔══██║██╔═══╝ ██╔═══╝
╚███╔███╔╝██║  ██║██║  ██║   ██║   ███████║██║  ██║██║     ██║
 ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝

██████╗ ██╗   ██╗███████╗██╗███╗   ██╗███████╗███████╗███████╗     █████╗ ██████╗ ██╗
██╔══██╗██║   ██║██╔════╝██║████╗  ██║██╔════╝██╔════╝██╔════╝    ██╔══██╗██╔══██╗██║
██████╔╝██║   ██║███████╗██║██╔██╗ ██║█████╗  ███████╗███████╗    ███████║██████╔╝██║
██╔══██╗██║   ██║╚════██║██║██║╚██╗██║██╔══╝  ╚════██║╚════██║    ██╔══██║██╔═══╝ ██║
██████╔╝╚██████╔╝███████║██║██║ ╚████║███████╗███████║███████║    ██║  ██║██║     ██║
╚═════╝  ╚═════╝ ╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝╚══════╝╚══════╝    ╚═╝  ╚═╝╚═╝     ╚═╝

WhatsApp Business API Integration Guide

Focus: Reach Kenyan/East-African users on their dominant chat app via two paths — Meta Cloud API (direct, lowest cost, full control) or Twilio (a wrapper that unifies WhatsApp with SMS/Voice and smooths webhooks). The operational crux for both is the 24-hour session window + pre-approved template messages.

Overview

Here's the big picture at a glance — two routes to the same WhatsApp user, with the 24-hour window deciding session vs template:

flowchart TD
  App["Your app"] --> Meta["Meta Cloud API<br/>REST Graph API"]
  App --> Twilio["Twilio<br/>messages.create wrapper"]
  Meta --> WA["WhatsApp"]
  Twilio --> WA
  WA --> User["Kenyan user"]
  User -->|"inbound message"| Webhook["Your webhook<br/>verify signature"]
  Webhook -->|"within 24h"| Session["Free-form session message"]
  Webhook -->|"outside 24h"| Template["Pre-approved template message"]

WhatsApp Business has no single SDK — it's a platform with two common integration routes:

Key rule (both paths): you may send free-form session messages only within 24 hours of the user's last inbound message. Outside that window you must send a pre-approved template message (templates are submitted to Meta and categorized: marketing, utility, authentication).

Official Documentation

Resource URL
Meta Cloud API https://developers.facebook.com/docs/whatsapp/cloud-api
Cloud API — send messages https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages
Cloud API — webhooks https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks
Business Management API — message templates https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/
Twilio WhatsApp https://www.twilio.com/docs/whatsapp
Twilio Node SDK https://github.com/twilio/twilio-node

Path A — Meta Cloud API (REST)

You've got this — here's the full Cloud API round trip, from opening a conversation with a template to handling the user's reply inside the 24h window:

sequenceDiagram
  participant You as "Your server"
  participant Graph as "Graph API"
  participant WA as "WhatsApp"
  participant User as "User"
  You->>Graph: POST messages type template
  Graph->>WA: deliver template message
  WA->>User: order_confirmation
  User->>WA: replies within 24h
  WA->>You: webhook with X-Hub-Signature-256
  You->>You: verify HMAC-SHA256 of raw body
  You->>Graph: POST messages type text free-form
  Graph->>User: Hello

Get a Phone Number ID + System User Access Token from the Meta dashboard, then POST to the Graph API:

# Free-form text (only valid inside the 24h window)
curl 'https://graph.facebook.com/v26.0/<PHONE_NUMBER_ID>/messages' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' -H 'Content-Type: application/json' \
  -d '{
    "messaging_product": "whatsapp",
    "recipient_type": "individual",
    "to": "254712345678",
    "type": "text",
    "text": { "body": "Hello!" }
  }'
# Template message (required to OPEN a conversation / outside the 24h window)
curl 'https://graph.facebook.com/v26.0/<PHONE_NUMBER_ID>/messages' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' -H 'Content-Type: application/json' \
  -d '{
    "messaging_product": "whatsapp",
    "to": "254712345678",
    "type": "template",
    "template": { "name": "order_confirmation", "language": { "code": "en" } }
  }'

Verify inbound webhooks with the X-Hub-Signature-256 header (HMAC-SHA256 of the raw body using your app secret) before processing.

Creating & getting templates approved

The order_confirmation template above doesn't exist until you submit it to Meta and it's approved. Every template carries a category — UTILITY (transactional: receipts, OTP-free order updates), MARKETING (promos, re-engagement), or AUTHENTICATION (one-time passcodes) — and Meta prices and reviews them by category. After you submit, the template enters PENDING, then moves to APPROVED or REJECTED (with a rejection reason); only an APPROVED template can be sent. Templates are created against your WhatsApp Business Account (WABA) ID, not the Phone Number ID used to send.

flowchart TD
  Draft["Draft template<br/>name · language · category"] --> Submit["POST WABA_ID message_templates"]
  Submit --> Pending["PENDING<br/>under Meta review"]
  Pending --> Approved["APPROVED<br/>safe to send"]
  Pending --> Rejected["REJECTED<br/>fix and resubmit"]
  Approved --> Send["Send via Phone Number ID"]
# Create a UTILITY template (submitted for approval -> PENDING)
curl 'https://graph.facebook.com/v26.0/<WABA_ID>/message_templates' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' -H 'Content-Type: application/json' \
  -d '{
    "name": "order_confirmation",
    "language": "en",
    "category": "UTILITY",
    "components": [
      {
        "type": "BODY",
        "text": "Hi {{1}}, your order {{2}} is confirmed. Asante!",
        "example": { "body_text": [["Amani", "KE-1024"]] }
      },
      { "type": "FOOTER", "text": "codeAmani Labs" }
    ]
  }'

The {{1}}/{{2}} positional placeholders require an example so reviewers can see real values; when you later send, you fill them via the template's parameters. Check status before sending — filter by name on the WABA endpoint:

# Confirm APPROVED before sending outside the 24h window
curl 'https://graph.facebook.com/v26.0/<WABA_ID>/message_templates?name=order_confirmation' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'
# -> data[].status: PENDING | APPROVED | REJECTED

Gotcha: outside the 24-hour window you can send only APPROVED templates — a free-form text or a PENDING/REJECTED template will be dropped. Categorize honestly: MARKETING templates cost more than UTILITY and a marketing-flavored message submitted as UTILITY will be re-categorized or rejected by Meta, breaking your send flow. (Twilio users: the same Meta-approved templates apply, referenced by contentSid instead of name.)

Path B — Twilio

npm install twilio        # or: pip install twilio
const client = require("twilio")(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN,
);

await client.messages.create({
  from: "whatsapp:+14155238886",     // Twilio WhatsApp sandbox / your approved sender
  to: "whatsapp:+254712345678",      // note the whatsapp: prefix + E.164 with +
  body: "Hello from codeAmani",      // session message; use contentSid for templates
});

Twilio signs inbound webhooks with X-Twilio-Signature — validate it with the SDK's validateRequest helper.

codeAmani notes

Official docs: