WhatsApp Business API Integration Guide
What is the WhatsApp Business API?
Meta Cloud API or Twilio BSP — templates + 24h sessions + interactive buttons.
Two paths: Meta's *Cloud API* (direct, cheaper at scale, more setup overhead) or *Twilio* (faster onboarding, slightly higher per-message). Templates carry pre-approved content with variable slots (`{{1}}`, `{{2}}`); sessions are free-form text/media within 24h of the user's last inbound. Interactive message types: list pickers, reply buttons, call-to-action buttons (link/phone). Webhooks deliver inbound messages + delivery status — verify the `X-Hub-Signature-256` header on every call. For codeAmani, WhatsApp is the channel of choice for any East-African user-facing notification flow — far higher engagement than SMS or email.
Five WhatsApp primitives
Templates for outbound. 24h windows for free-form. Webhooks for everything inbound.
██╗ ██╗██╗ ██╗ █████╗ ████████╗███████╗ █████╗ ██████╗ ██████╗
██║ ██║██║ ██║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗
██║ █╗ ██║███████║███████║ ██║ ███████╗███████║██████╔╝██████╔╝
██║███╗██║██╔══██║██╔══██║ ██║ ╚════██║██╔══██║██╔═══╝ ██╔═══╝
╚███╔███╔╝██║ ██║██║ ██║ ██║ ███████║██║ ██║██║ ██║
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
██████╗ ██╗ ██╗███████╗██╗███╗ ██╗███████╗███████╗███████╗ █████╗ ██████╗ ██╗
██╔══██╗██║ ██║██╔════╝██║████╗ ██║██╔════╝██╔════╝██╔════╝ ██╔══██╗██╔══██╗██║
██████╔╝██║ ██║███████╗██║██╔██╗ ██║█████╗ ███████╗███████╗ ███████║██████╔╝██║
██╔══██╗██║ ██║╚════██║██║██║╚██╗██║██╔══╝ ╚════██║╚════██║ ██╔══██║██╔═══╝ ██║
██████╔╝╚██████╔╝███████║██║██║ ╚████║███████╗███████║███████║ ██║ ██║██║ ██║
╚═════╝ ╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝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:
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:
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/v23.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/v23.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.
# Create a UTILITY template (submitted for approval -> PENDING)
curl 'https://graph.facebook.com/v23.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/v23.0/<WABA_ID>/message_templates?name=order_confirmation' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
# -> data[].status: PENDING | APPROVED | REJECTEDGotcha: 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 twilioconst 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.