WABA Portal API Documentation
Complete API reference for integrating WhatsApp Business messaging into your applications.
Get your API Key
Login to your account and generate an API key from the API Integration page.
Make your first API call
Use your API key to authenticate and start sending messages.
Configure webhooks
Set up webhooks to receive incoming messages and delivery status updates.
Base URL
https://your-domain.com/api/v1Authentication
All API requests require authentication using a Bearer token in the Authorization header.
Include your API key in every request using the Authorization header:
curl -X POST https://your-domain.com/api/v1/messages/text \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "919876543210", "message": "Hello!"}'Security Note: Keep your API key secure. Never expose it in client-side code or public repositories.
Send Messages
/api/v1/messages/textRequest Body
{
"to": "919876543210", // Phone number with country code (no + or spaces)
"message": "Hello! This is a test message from WABA Portal."
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| to | string | Yes | Recipient phone number with country code |
| message | string | Yes | Message content (max 4096 characters) |
Success Response
{
"success": true,
"messageId": "wamid.HBgLOTE5ODc2NTQzMjEwFQIAERgSM0VCMDA..."
}Error Response
{
"error": "Invalid phone number format"
}Code Examples
curl -X POST https://your-domain.com/api/v1/messages/text \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"message": "Hello from WABA Portal!"
}'Mark Message as Read
Mark an incoming message as read. This shows the blue checkmarks to the sender, indicating that you have seen their message. Optionally show a typing indicator to simulate that you are composing a response.
/api/v1/messages/mark-readRequest Body
{
"messageId": "wamid.HBgMOTE5MDMwMzgzNDM4FQIAEhgg...",
"showTypingIndicator": true // Optional
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| messageId | string | Yes | The message ID received in the webhook payload (starts with "wamid.") |
| showTypingIndicator | boolean | No | Show "typing..." indicator to the user (auto-dismisses after 25 seconds or when you send a message) |
Success Response
{
"success": true
}Code Examples
curl -X POST https://your-domain.com/api/v1/messages/mark-read \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageId": "wamid.HBgMOTE5MDMwMzgzNDM4FQIAEhgg...",
"showTypingIndicator": true
}'Tip: Use the typing indicator when processing takes time. Mark the message as read with showTypingIndicator: true, process the message, then send your reply. The typing indicator auto-dismisses after 25 seconds or when you send a message.
Conversations API
Retrieve conversation history and messages. These APIs support cursor-based pagination for efficient infinite scroll implementation.
/api/v1/conversationsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| cursor | string | - | Pagination cursor (from previous response) |
| limit | number | 20 | Number of conversations to return (max: 50) |
Example Request
curl -X GET "https://your-domain.com/api/v1/conversations?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"data": [
{
"phoneNumber": "919876543210",
"contactName": "John Doe",
"lastMessage": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Hello, I need help with my order",
"type": "text",
"direction": "inbound",
"timestamp": "2025-12-01T10:30:00.000Z"
},
"unreadCount": 3
},
{
"phoneNumber": "919123456789",
"contactName": "Jane Smith",
"lastMessage": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"content": "[Image] Product photo",
"type": "image",
"direction": "inbound",
"timestamp": "2025-12-01T09:15:00.000Z"
},
"unreadCount": 0
}
],
"pagination": {
"nextCursor": "eyJ0aW1lc3RhbXAiOi4uLn0=",
"hasMore": true
}
}Code Examples
# First page
curl -X GET "https://your-domain.com/api/v1/conversations?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"
# Next page (use nextCursor from response)
curl -X GET "https://your-domain.com/api/v1/conversations?limit=20&cursor=CURSOR" \
-H "Authorization: Bearer YOUR_API_KEY"/api/v1/conversations/:phoneNumber/messagesPath Parameters
| Parameter | Type | Description |
|---|---|---|
| phoneNumber | string | Contact phone number with country code (e.g., 919876543210) |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| cursor | string | - | Pagination cursor (from previous response) |
| limit | number | 30 | Number of messages to return (max: 100) |
Example Request
curl -X GET "https://your-domain.com/api/v1/conversations/919876543210/messages?limit=30" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"waMessageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSM0YwNjU0OUE1QTY1QTc4NTVB",
"direction": "inbound",
"type": "text",
"content": {
"text": { "body": "Hello, I need help with my order" }
},
"status": "read",
"timestamp": "2025-12-01T10:30:00.000Z",
"contactName": "John Doe"
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"waMessageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSM0YwNjU0OUE1QTY1QTc4NTVB",
"direction": "outbound",
"type": "text",
"content": {
"text": { "body": "Hi! I'd be happy to help. What's your order number?" }
},
"status": "delivered",
"timestamp": "2025-12-01T10:31:00.000Z",
"contactName": null
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"waMessageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSM0YwNjU0OUE1QTY1QTc4NTVB",
"direction": "inbound",
"type": "image",
"content": {
"image": {
"id": "1234567890",
"mime_type": "image/jpeg",
"caption": "Here is my order screenshot"
}
},
"status": "read",
"timestamp": "2025-12-01T10:32:00.000Z",
"contactName": "John Doe"
}
],
"pagination": {
"nextCursor": "eyJ0aW1lc3RhbXAiOi4uLn0=",
"hasMore": true
}
}Code Examples
# First page (latest messages)
curl -X GET "https://your-domain.com/api/v1/conversations/919876543210/messages?limit=30" \
-H "Authorization: Bearer YOUR_API_KEY"
# Load older messages (scroll up)
curl -X GET "https://your-domain.com/api/v1/conversations/919876543210/messages?cursor=CURSOR" \
-H "Authorization: Bearer YOUR_API_KEY"Infinite Scroll: Messages are returned in reverse chronological order (newest first). When implementing infinite scroll, load the first page on initial render, then use the nextCursor to load older messages as the user scrolls up.
Templates API
Manage WhatsApp message templates programmatically.
/api/v1/templatescurl -X GET https://your-domain.com/api/v1/templates \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"data": [
{
"id": "123456789",
"name": "order_confirmation",
"status": "APPROVED",
"category": "UTILITY",
"language": "en",
"components": [
{ "type": "HEADER", "format": "IMAGE" },
{ "type": "BODY", "text": "Hi {{1}}, your order {{2}} is confirmed!" },
{ "type": "FOOTER", "text": "Thank you for shopping" },
{ "type": "BUTTONS", "buttons": [...] }
]
}
]
}/api/v1/templatesexample field with sample values. This is required by WhatsApp for template approval. Only TEXT format headers are supported.Simple Template (Body Only)
curl -X POST https://your-domain.com/api/v1/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "simple_welcome",
"category": "UTILITY",
"language": "en",
"components": [
{
"type": "BODY",
"text": "Thank you for contacting us. We will get back to you shortly."
},
{
"type": "FOOTER",
"text": "Reply STOP to unsubscribe"
}
]
}'Template With Text Header
curl -X POST https://your-domain.com/api/v1/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "order_update",
"category": "UTILITY",
"language": "en",
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Order Update"
},
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} has been shipped!",
"example": {
"body_text": [["John", "ORD-12345"]]
}
},
{
"type": "FOOTER",
"text": "Thank you for shopping"
}
]
}'Template With Variables (Required Example)
curl -X POST https://your-domain.com/api/v1/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "personalized_greeting",
"category": "MARKETING",
"language": "en",
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, welcome to {{2}}! Your account is now active.",
"example": {
"body_text": [["John", "Acme Corp"]]
}
},
{
"type": "FOOTER",
"text": "Reply STOP to unsubscribe"
}
]
}'/api/v1/templates/:id/editid field (not the name) when editing templates. Only REJECTED templates can be edited.curl -X POST https://your-domain.com/api/v1/templates/123456789/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"category": "MARKETING",
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Spring Sale!"
},
{
"type": "BODY",
"text": "Shop now through {{1}} and use code {{2}} to get {{3}} off.",
"example": {
"body_text": [["the end of April", "25OFF", "25%"]]
}
},
{
"type": "FOOTER",
"text": "Use the buttons below to manage your subscriptions"
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "QUICK_REPLY",
"text": "Unsubscribe from Promos"
}
]
}
]
}'/api/v1/templates/:namecurl -X DELETE https://your-domain.com/api/v1/templates/welcome_message \
-H "Authorization: Bearer YOUR_API_KEY"Template Types
WhatsApp supports various template types for different use cases. You can create templates with TEXT headers through this API. Templates with media headers (Image, Video, Document) can be created via Meta Business Manager and sent through this API.
Without Variables
{
"name": "simple_greeting",
"category": "MARKETING",
"language": "en",
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Welcome to Our Store"
},
{
"type": "BODY",
"text": "Thank you for visiting! Check out our latest products."
},
{
"type": "FOOTER",
"text": "Reply STOP to unsubscribe"
}
]
}With Variables & Buttons
{
"name": "order_update",
"category": "UTILITY",
"language": "en",
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Order Update"
},
{
"type": "BODY",
"text": "Hello {{1}}, your order {{2}} has been shipped!\nExpected delivery: {{3}}",
"example": {
"body_text": [["John", "ORD-12345", "Tomorrow"]]
}
},
{
"type": "FOOTER",
"text": "Thank you for shopping with us"
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "QUICK_REPLY",
"text": "Track Order"
},
{
"type": "URL",
"text": "View Details",
"url": "https://example.com/orders/{{1}}",
"example": ["ORD-12345"]
},
{
"type": "PHONE_NUMBER",
"text": "Call Support",
"phone_number": "+919876543210"
}
]
}
]
}Webhooks
Configure webhooks to receive real-time notifications for incoming messages and delivery status updates. Your webhook endpoint will receive POST requests whenever a user sends you a message or when message delivery status changes.
/api/v1/webhookGet current webhook configuration for your project.
curl -X GET https://your-domain.com/api/v1/webhook \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"success": true,
"data": {
"phoneNumber": "919876543210",
"webhookUrl": "https://your-server.com/webhook",
"webhookSecret": "a1b2c3d4e5f6...",
"webhookEnabled": true,
"webhookEvents": ["messages", "statuses"]
}
}/api/v1/webhookUpdate webhook configuration. A webhook secret is automatically generated when enabling webhooks for the first time.
curl -X PUT https://your-domain.com/api/v1/webhook \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-server.com/webhook",
"webhookEnabled": true,
"webhookEvents": ["messages", "statuses"]
}'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| webhookUrl | string | Yes | HTTPS URL to receive webhook events (empty string to clear) |
| webhookEnabled | boolean | Yes | Enable or disable webhook delivery |
| webhookEvents | array | No | Events to receive: ["messages"], ["statuses"], or ["messages", "statuses"] |
Response
{
"success": true,
"data": {
"phoneNumber": "919876543210",
"webhookUrl": "https://your-server.com/webhook",
"webhookEnabled": true,
"webhookEvents": ["messages", "statuses"],
"webhookSecret": "a1b2c3d4e5f6..."
}
}Note: The phone number must be verified by WhatsApp before you can configure webhooks. If not verified, the API will return an error.
/api/v1/webhook/testSend a test webhook to verify your endpoint is correctly configured and reachable.
curl -X POST https://your-domain.com/api/v1/webhook/test \
-H "Authorization: Bearer YOUR_API_KEY"Success Response
{
"success": true,
"data": {
"webhookUrl": "https://your-server.com/webhook",
"statusCode": 200,
"statusText": "OK",
"duration": "245ms",
"responseBody": "{"received":true}",
"testPayload": {
"event": "test",
"timestamp": "2024-01-15T10:30:00Z",
"projectId": "your-project-id",
"data": {
"message": "This is a test webhook from WABA Portal",
"testId": "abc123..."
}
}
}
}Error Response
{
"success": false,
"error": "Webhook endpoint returned status 404",
"data": {
"webhookUrl": "https://your-server.com/webhook",
"statusCode": 404,
"statusText": "Not Found",
"duration": "123ms"
}
}/api/v1/webhookDisable and clear all webhook configuration.
curl -X DELETE https://your-domain.com/api/v1/webhook \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"success": true
}Verifying Webhook Signatures
Each webhook request includes an HMAC signature in the X-Webhook-Signature header. Verify this signature to ensure the webhook is authentic and hasn't been tampered with.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhook', express.json(), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookSecret = 'your-webhook-secret';
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the verified webhook...
res.json({ received: true });
});Tip: The webhook secret is returned when you first enable webhooks. Store it securely and use it to verify all incoming webhooks.
Create a webhook endpoint
Set up a POST endpoint on your server to receive webhook events. The endpoint must be publicly accessible via HTTPS.
Configure your webhook URL
Use the API or go to your project settings to enter your webhook URL. Enable the webhook and select which events you want to receive (messages, statuses, or both).
Test your webhook
Use the POST /api/v1/webhook/test endpoint to send a test webhook and verify your endpoint is working correctly.
Start receiving events
Once configured, your endpoint will receive POST requests for incoming messages and status updates in real-time.
Important: Your endpoint must respond with HTTP 200 within 5 seconds. Use async processing for any long-running tasks.
When a user sends a text message to your WhatsApp number:
{
"event": "message",
"timestamp": "2024-01-15T10:30:00Z",
"projectId": "your-project-id",
"data": {
"from": "919876543210",
"messageId": "wamid.HBgMOTE5MDMwMzgzNDM4FQIAEhgg...",
"timestamp": "1705314600",
"type": "text",
"contact": {
"name": "John Doe",
"wa_id": "919876543210"
},
"text": {
"body": "Hello, I need help with my order"
}
}
}const express = require('express');
const app = express();
app.post('/webhook', express.json(), (req, res) => {
const { event, data, projectId, timestamp } = req.body;
console.log('Webhook received:', event, 'at', timestamp);
if (event === 'message') {
console.log('New message from:', data.from);
console.log('Message type:', data.type);
// Handle different message types
switch (data.type) {
case 'text':
console.log('Text:', data.text.body);
break;
case 'image':
console.log('Image received, ID:', data.image.id);
break;
case 'button':
console.log('Button clicked:', data.button.text);
break;
// Handle other types...
}
// Check if this is a reply to a previous message
if (data.context) {
console.log('Reply to message:', data.context.id);
}
} else if (event === 'status') {
console.log('Message', data.messageId, 'status:', data.status);
if (data.status === 'failed' && data.errors) {
console.error('Delivery failed:', data.errors);
}
}
// Respond quickly - do heavy processing async
res.status(200).json({ received: true });
});
app.listen(3000);Respond quickly
Return HTTP 200 within 5 seconds. Process webhooks asynchronously using a message queue for heavy operations.
Handle duplicates
Use messageId to deduplicate webhooks. The same webhook may be delivered multiple times in rare cases.
Use HTTPS
Your webhook endpoint must use HTTPS with a valid SSL certificate. HTTP endpoints are not supported.
Log webhook data
Log incoming webhooks for debugging. This helps troubleshoot issues and track message flow.
Error Handling
Understanding API errors and how to handle them.
{
"error": "Error message description"
}| Code | Status | Description |
|---|---|---|
200 | Success | Request completed successfully |
400 | Bad Request | Invalid request body or parameters |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | API key doesn't have permission for this action |
404 | Not Found | Requested resource not found |
429 | Rate Limited | Too many requests, please slow down |
500 | Server Error | Internal server error |
Invalid phone number format
Phone numbers must include country code without + or spaces (e.g., 919876543210)
Template not found
Verify the template name is correct and has been approved by WhatsApp
Outside 24-hour messaging window
Text messages can only be sent within 24 hours of the last user message. Use approved templates for initiating conversations.
Invalid template parameters
Ensure all required template variables are provided and match the expected format
Media URL not accessible
Media URLs must be publicly accessible. WhatsApp servers need to download the media.
Invalid parameter
This error occurs when creating templates. Common causes:
- Missing
examplefield for templates with variables ({{1}}, {{2}}) - Template name already exists with a different category (delete old template first)
- Invalid button format or missing required button parameters
Template category doesn't match
Cannot change the category of an existing template. Either delete the old template first or use a different template name.
Component parameter count mismatch
When sending template messages, the number of parameters must match the number of variables in the template. For example, if template body has {{1}} and {{2}}, you must provide exactly 2 parameters.