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:
- Meta Cloud API — pure REST against the Graph API. Lowest per-message cost and full control; you manage access tokens, template submission, and webhook signature verification yourself.
- Twilio — the
twiliohelper library (Node/Python) wraps WhatsApp behind the samemessages.createinterface as SMS, withwhatsapp:prefixes and built-in webhook tooling. Faster to ship; priced above Meta's raw rates.
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
- Highest-reach channel in Kenya: WhatsApp is the default chat app — pair it with M-Pesa
for conversational commerce (browse/order over WhatsApp, charge via Daraja STK Push,
confirm via a utility template). Complements the SMS/USSD reach of
africas-talking. - Phone format trap: WhatsApp uses E.164 with
+(+254…); Twilio additionally needs thewhatsapp:prefix. The repo's Daraja rule is254…(no+) — normalize per-API. SeeMPESA_PATTERNS.md. - Pick a path: default to Meta Cloud API for cost + control once volume justifies the webhook/template work; reach for Twilio to ship fast or to keep one vendor across SMS/Voice/WhatsApp.
- Security: access tokens / auth tokens are server-side only (
.env.local/ Vercel env, or move them into Infisical). Always verify webhook signatures (X-Hub-Signature-256for Meta,X-Twilio-Signaturefor Twilio) before acting on a payload. - Templates need approval: budget lead time — marketing/utility/auth templates are reviewed by Meta before they can send.
Official docs: