WABA Portal API Documentation

Complete API reference for integrating WhatsApp Business messaging into your applications.

Quick Start
Get up and running in minutes
1

Get your API Key

Login to your account and generate an API key from the API Integration page.

2

Make your first API call

Use your API key to authenticate and start sending messages.

3

Configure webhooks

Set up webhooks to receive incoming messages and delivery status updates.

Base URL

https://your-domain.com/api/v1

Authentication

All API requests require authentication using a Bearer token in the Authorization header.

API Key Authentication

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

POST
/api/v1/messages/text
Send a plain text message to a WhatsApp user

Request Body

{
  "to": "919876543210",      // Phone number with country code (no + or spaces)
  "message": "Hello! This is a test message from WABA Portal."
}

Parameters

ParameterTypeRequiredDescription
tostringYesRecipient phone number with country code
messagestringYesMessage 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!"
  }'
Free Flow Messages: Text, images, videos, documents, audio, and location messages can be sent within the 24-hour messaging window after a user messages you. To initiate conversations outside this window, use template messages.

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.

POST
/api/v1/messages/mark-read
Mark a message as read by its ID

Request Body

{
  "messageId": "wamid.HBgMOTE5MDMwMzgzNDM4FQIAEhgg...",
  "showTypingIndicator": true  // Optional
}

Parameters

ParameterTypeRequiredDescription
messageIdstringYesThe message ID received in the webhook payload (starts with "wamid.")
showTypingIndicatorbooleanNoShow "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.

GET
/api/v1/conversations
List all conversations (unique phone numbers) with preview and unread count

Query Parameters

ParameterTypeDefaultDescription
cursorstring-Pagination cursor (from previous response)
limitnumber20Number 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"
GET
/api/v1/conversations/:phoneNumber/messages
Get message history for a specific conversation

Path Parameters

ParameterTypeDescription
phoneNumberstringContact phone number with country code (e.g., 919876543210)

Query Parameters

ParameterTypeDefaultDescription
cursorstring-Pagination cursor (from previous response)
limitnumber30Number 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.

GET
/api/v1/templates
Retrieve all templates for your project
curl -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": [...] }
      ]
    }
  ]
}
POST
/api/v1/templates
Create a new message template
Important: Templates with variables (like {{1}}, {{2}}) MUST include an example 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"
      }
    ]
  }'
POST
/api/v1/templates/:id/edit
Edit an existing template
Important: Use the template id 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"
          }
        ]
      }
    ]
  }'
DELETE
/api/v1/templates/:name
Delete a template by name
curl -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.

Text Template
Simple text-based templates with optional variables

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.

Webhook Configuration API
Programmatically configure and test webhooks using the API
GET
/api/v1/webhook

Get 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"]
  }
}
PUT
/api/v1/webhook

Update 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
ParameterTypeRequiredDescription
webhookUrlstringYesHTTPS URL to receive webhook events (empty string to clear)
webhookEnabledbooleanYesEnable or disable webhook delivery
webhookEventsarrayNoEvents 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.

POST
/api/v1/webhook/test

Send 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"
  }
}
DELETE
/api/v1/webhook

Disable 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.

Webhook Setup
How to configure your webhook endpoint
1

Create a webhook endpoint

Set up a POST endpoint on your server to receive webhook events. The endpoint must be publicly accessible via HTTPS.

2

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).

3

Test your webhook

Use the POST /api/v1/webhook/test endpoint to send a test webhook and verify your endpoint is working correctly.

4

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.

Webhook Payload Format
Understanding the webhook payload structure

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"
    }
  }
}
Handling Webhooks
Example code for processing webhook events
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);
Best Practices

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 Response Format
{
  "error": "Error message description"
}
HTTP Status Codes
CodeStatusDescription
200
SuccessRequest completed successfully
400
Bad RequestInvalid request body or parameters
401
UnauthorizedMissing or invalid API key
403
ForbiddenAPI key doesn't have permission for this action
404
Not FoundRequested resource not found
429
Rate LimitedToo many requests, please slow down
500
Server ErrorInternal server error
Common Errors

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 example field 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.