WhatsApp Business API for Developers: Setup and First Message
The WhatsApp Cloud API is Meta's hosted version of the WhatsApp Business API. You create a Meta developer account, set up a WhatsApp Business app, get a temporary phone number for testing, and send messages via REST API calls. The Cloud API is free to set up, and Meta hosts the infrastructure. You only pay per conversation once you start using a production phone number.
Why WhatsApp matters for Kenyan developers
WhatsApp is the most used messaging app in Kenya. People use it to chat with family, coordinate work, share documents, and increasingly to interact with businesses. A customer who ignores your email will read your WhatsApp message within minutes.
For developers building products in East Africa, the WhatsApp Business API opens up:
- Order notifications: send delivery updates, payment confirmations, booking reminders
- Customer support: let customers ask questions and get responses in the app they already use all day
- Chatbots: automate common queries (pricing, hours, FAQs) without human agents
- Two-factor authentication: send OTP codes via WhatsApp instead of SMS (cheaper and more reliable)
- Marketing: send product announcements and promotional messages (with user opt-in)
There are two paths to the WhatsApp Business API: the Cloud API (hosted by Meta, free to set up) and the On-Premise API (you host the infrastructure). For most developers, the Cloud API is the right choice. It is simpler, cheaper to start, and Meta handles uptime.
Setting up your Meta Developer account
The setup involves creating a Meta app and configuring the WhatsApp Business product within it.
- Go to
developers.facebook.comand log in with a Facebook account - Click "Create App" and select "Business" as the app type
- Enter a name for your app (e.g., "MyApp WhatsApp")
- On the app dashboard, find "WhatsApp" in the product list and click "Set Up"
- Meta provides a test phone number and a temporary access token on the API Setup page
The temporary access token expires after 24 hours. For production, you need a permanent System User token. But for learning and testing, the temporary token works fine.
Store your credentials:
# .env
WHATSAPP_TOKEN=your_temporary_access_token
WHATSAPP_PHONE_ID=your_phone_number_id
WHATSAPP_VERIFY_TOKEN=my_secret_verify_token_123The WHATSAPP_PHONE_ID is the Phone Number ID from the API Setup page. This is not the phone number itself but a numeric identifier Meta assigns to it.
Sending your first message
With the Cloud API, sending a message is a single POST request. You need the recipient's phone number in international format (e.g., 254712345678 for a Kenyan number).
Before you can message someone, they must have sent a message to your WhatsApp Business number first, or you must use an approved message template. For testing, Meta lets you send to up to 5 phone numbers you add in the developer portal.
Node.js:
import axios from 'axios';
const WHATSAPP_API = 'https://graph.facebook.com/v18.0';
async function sendTextMessage(
to: string,
message: string
) {
const phoneId = process.env.WHATSAPP_PHONE_ID;
const token = process.env.WHATSAPP_TOKEN;
const response = await axios.post(
`${WHATSAPP_API}/${phoneId}/messages`,
{
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: to,
type: 'text',
text: { body: message },
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// Send a message
sendTextMessage(
'254712345678',
'Hello from McTaba Labs! Your order is confirmed.'
).then(console.log);Python:
import requests
import os
WHATSAPP_API = "https://graph.facebook.com/v18.0"
def send_text_message(to: str, message: str) -> dict:
phone_id = os.environ["WHATSAPP_PHONE_ID"]
token = os.environ["WHATSAPP_TOKEN"]
response = requests.post(
f"{WHATSAPP_API}/{phone_id}/messages",
json={
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": to,
"type": "text",
"text": {"body": message},
},
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
)
response.raise_for_status()
return response.json()
# Send a message
result = send_text_message(
"254712345678",
"Hello from McTaba Labs! Your order is confirmed.",
)
print(result)A successful response returns a message ID and the recipient's WhatsApp ID. If the recipient's number is not on WhatsApp, you get an error.
Receiving messages via webhooks
To receive messages that customers send to your WhatsApp Business number, you configure a webhook. Meta sends a POST request to your endpoint every time someone messages your number.
Webhook setup has two parts: a verification handshake and the actual message handling.
Node.js (Express):
import express from 'express';
const app = express();
app.use(express.json());
const VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN!;
// Webhook verification (GET)
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
console.log('Webhook verified');
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
});
// Receive messages (POST)
app.post('/webhook', (req, res) => {
const entry = req.body.entry?.[0];
const changes = entry?.changes?.[0];
const value = changes?.value;
if (value?.messages) {
const message = value.messages[0];
const from = message.from; // sender phone number
const type = message.type;
if (type === 'text') {
const text = message.text.body;
console.log(`Message from ${from}: ${text}`);
// TODO: process message and send reply
}
}
// Always return 200 to acknowledge
res.sendStatus(200);
});
app.listen(3000);Python (Flask):
from flask import Flask, request
import os
app = Flask(__name__)
VERIFY_TOKEN = os.environ["WHATSAPP_VERIFY_TOKEN"]
@app.route("/webhook", methods=["GET"])
def verify():
mode = request.args.get("hub.mode")
token = request.args.get("hub.verify_token")
challenge = request.args.get("hub.challenge")
if mode == "subscribe" and token == VERIFY_TOKEN:
return challenge, 200
return "Forbidden", 403
@app.route("/webhook", methods=["POST"])
def webhook():
data = request.get_json()
entry = data.get("entry", [{}])[0]
changes = entry.get("changes", [{}])[0]
value = changes.get("value", {})
messages = value.get("messages", [])
if messages:
message = messages[0]
sender = message["from"]
msg_type = message["type"]
if msg_type == "text":
text = message["text"]["body"]
print(f"Message from {sender}: {text}")
# TODO: process and reply
return "OK", 200
if __name__ == "__main__":
app.run(port=3000)To register your webhook URL in the Meta developer portal: go to your app, click "WhatsApp" > "Configuration," enter your webhook URL (must be HTTPS), and provide the verify token you chose.
The 24-hour window and message templates
WhatsApp enforces a 24-hour reply window. After a customer messages you, you have 24 hours to reply with any content (text, images, documents). After 24 hours, you can only send pre-approved message templates.
This is a deliberate design to prevent spam. It means:
- Customer-initiated conversations: you can reply freely within 24 hours. This covers support requests, questions, orders.
- Business-initiated conversations: if you want to message someone who has not messaged you recently, you must use a template. Templates must be submitted to Meta for approval before use.
Creating a message template:
- Go to your WhatsApp Business Manager
- Navigate to "Message Templates"
- Create a new template with a name, language, and category (marketing, utility, or authentication)
- Write the template text with variables: "Hello {{1}}, your order {{2}} has been shipped."
- Submit for review (approval takes minutes to 24 hours)
Sending a template message:
async function sendTemplateMessage(
to: string,
templateName: string,
params: string[]
) {
const phoneId = process.env.WHATSAPP_PHONE_ID;
const token = process.env.WHATSAPP_TOKEN;
const response = await axios.post(
`${WHATSAPP_API}/${phoneId}/messages`,
{
messaging_product: 'whatsapp',
to: to,
type: 'template',
template: {
name: templateName,
language: { code: 'en' },
components: [
{
type: 'body',
parameters: params.map(p => ({
type: 'text',
text: p,
})),
},
],
},
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// Usage: send order update
sendTemplateMessage(
'254712345678',
'order_shipped',
['John', 'ORD-12345']
);Meta charges per conversation, not per message. A conversation opens when you send a message (either within the 24-hour window or via template) and lasts 24 hours. [TODO: verify on provider website] for current per-conversation pricing for Kenya.
Frequently Asked Questions
- Is the WhatsApp Cloud API free?
- Setting up and testing is free. Meta provides 1,000 free service conversations per month. Beyond that, you pay per conversation. The rate depends on the conversation category (marketing, utility, authentication, service) and the country. [TODO: verify on provider website] for current pricing.
- Can I use my personal WhatsApp number with the API?
- You can register your personal number as a WhatsApp Business number, but it will be disconnected from the regular WhatsApp app. You cannot use the same number for both personal WhatsApp and the Business API simultaneously. Most businesses get a new number for the API.
- What is the difference between the Cloud API and the On-Premise API?
- The Cloud API is hosted by Meta. You send API requests to Meta's servers and they handle the WhatsApp infrastructure. The On-Premise API requires you to run Docker containers on your own servers. The Cloud API is simpler and cheaper to start with. The On-Premise API gives you more control and is used by large enterprises with specific compliance requirements.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon. Ship 8 production apps with M-Pesa, USSD, and WhatsApp integrations, and get career support until placement.
See Programs