Introduction
The AiSender API is a REST API. Send requests over HTTPS with a JSON body, and you get JSON back. Messages go out on the official WhatsApp Business Platform (Cloud API) from the WhatsApp number you connected in your panel.
https://whatsapp.aisender.io/api/v1Before you start you need: a WhatsApp number connected in the panel, at least one template approved by Meta, wallet balance, and an API key.
Authentication
Create an API key in the panel under API & webhooks. The key starts with wcl_ and is shown only once, so store it safely on your server. Never put it in a website or mobile app that users can see.
Send the key in the Authorization header of every request:
Authorization: Bearer wcl_YOUR_API_KEY
Content-Type: application/json- You can have up to 10 active keys, for example one per website or app.
- Add allowed IPs to a key (single IPs or ranges like
203.0.113.0/24) so it only works from your server. - Revoke a key from the panel at any time. Apps using it stop working straight away.
Phone numbers
Send numbers with the country code. Spaces, dashes, a leading + or 00 are removed for you. A 10-digit Indian mobile number (with or without a leading 0) is treated as 91 + number.
| You send | We use |
|---|---|
+91 98123-45678 | 919812345678 |
09812345678 | 919812345678 |
9812345678 | 919812345678 |
+971 50 123 4567 | 971501234567 |
Send a template message
/messagesSend an approved template to one number. Use this for OTPs, order updates, reminders and offers.
| Field | Type | Description |
|---|---|---|
to required | string | Recipient's WhatsApp number with country code. |
template.name required | string | Name of an approved template, exactly as in the panel. |
template.language | string | Template language code, e.g. en, hi. Needed if the template exists in more than one language. |
template.params | array | Values for the template variables {{1}}, {{2}}… in order. The count must match the template. |
client_ref | string | Your own ID, up to 100 characters (e.g. order number). Sending the same client_ref again never sends twice. |
waba | string | Which of your connected numbers to send from, if you have more than one. |
curl -X POST https://whatsapp.aisender.io/api/v1/messages \
-H "Authorization: Bearer wcl_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "919812345678",
"template": {"name": "order_update", "language": "en", "params": ["Ravi", "#1043", "5 Nov"]},
"client_ref": "order-1043"}'const res = await fetch("https://whatsapp.aisender.io/api/v1/messages", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.AISENDER_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
to: "919812345678",
template: { name: "order_update", language: "en", params: ["Ravi", "#1043", "5 Nov"] },
client_ref: "order-1043",
}),
});
const data = await res.json();<?php
$ch = curl_init("https://whatsapp.aisender.io/api/v1/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("AISENDER_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"to" => "919812345678",
"template" => ["name" => "order_update", "language" => "en", "params" => ["Ravi", "#1043", "5 Nov"]],
"client_ref" => "order-1043",
]),
]);
$data = json_decode(curl_exec($ch), true);import os, requests
r = requests.post("https://whatsapp.aisender.io/api/v1/messages",
headers={"Authorization": "Bearer " + os.environ["AISENDER_KEY"]},
json={"to": "919812345678",
"template": {"name": "order_update", "language": "en", "params": ["Ravi", "#1043", "5 Nov"]},
"client_ref": "order-1043"})
print(r.json())Response 202 Accepted. The message is queued and sent within seconds. The price is taken from your wallet now.
{
"queued": 1,
"ids": [811],
"charged": 0.2,
"balance": 943.8
}If the client_ref was already used, you get 200 with "duplicate": true and the original message ID, and nothing is sent or charged again.
Send in bulk
/messagesSend one template to up to 1,000 numbers per request, each with its own variables. All of them are grouped as one campaign in your panel reports.
| Field | Type | Description |
|---|---|---|
recipients required | array | List of { "to": "…", "params": [ … ] }. A plain list of numbers also works if the template has no variables. |
template required | object | name and optional language, as above. |
campaign | string | Campaign name shown in reports, up to 120 characters. |
waba | string | Which connected number to send from. |
curl -X POST https://whatsapp.aisender.io/api/v1/messages \
-H "Authorization: Bearer wcl_YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"campaign": "Diwali offer",
"template": {"name": "diwali_offer", "language": "en"},
"recipients": [
{"to": "919812345678", "params": ["Ravi", "20%"]},
{"to": "919876543210", "params": ["Priya", "15%"]}
]}'{
"queued": 2,
"ids": [812, 813],
"campaign_id": 57,
"charged": 2.0,
"balance": 941.8,
"skipped": [ { "to": "12345", "reason": "invalid number" } ]
}Numbers that are invalid, repeated, blacklisted, or that replied STOP to your marketing are listed in skipped and are not charged.
Text replies
/messagesSend a free-form text instead of a template. WhatsApp allows this only within 24 hours of the customer's last message to you. Outside that window, use a template.
curl -X POST https://whatsapp.aisender.io/api/v1/messages \
-H "Authorization: Bearer wcl_YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"to": "919812345678", "text": "Thanks! Your order is on the way."}'Message status
/messages/{id}Get one message and its latest status.
{
"id": 811,
"direction": "out",
"to": "919812345678",
"type": "template",
"template": "order_update",
"text": "Hi Ravi, your order #1043 will be delivered on 5 Nov.",
"status": "delivered",
"price": 0.2,
"refunded": false,
"client_ref": "order-1043",
"created_at": "2026-11-02T10:30:00.000Z"
}| Status | Meaning |
|---|---|
queued | Accepted and waiting to be sent (or scheduled). |
sent | Handed to WhatsApp. |
delivered | Reached the customer's phone. |
read | The customer opened it (only if they have read receipts on). |
failed | Could not be delivered. See error. The price is refunded to your wallet. |
cancelled | A scheduled campaign was cancelled before sending. Refunded. |
List templates
/templatesReturns your templates with the number of variables each one needs. Add ?waba= to choose a connected number.
{
"waba": "919000000001",
"templates": [
{ "name": "order_update", "language": "en", "category": "UTILITY", "variables": 3,
"body": "Hi {{1}}, your order {{2}} will be delivered on {{3}}.", "supported": true }
]
}Wallet balance
/balance{ "balance": 941.8, "currency": "INR", "status": "active" }status is blocked when sending is paused, for example when the wallet is empty.
Incoming messages
/inbox?after_id=0&limit=100Poll for messages your customers sent, oldest first. Save next_after_id and send it as after_id next time. limit is up to 500. If you can receive webhooks, use them instead.
{
"messages": [
{ "id": 905, "direction": "in", "from": "919812345678", "name": "Ravi",
"type": "text", "text": "Is the offer still on?", "status": "received",
"created_at": "2026-11-02T10:41:00.000Z" }
],
"next_after_id": 905
}Webhooks
Add your HTTPS URL in the panel under API & webhooks. We send a POST with a JSON body when a customer messages you and when a message status changes. The URL must be public HTTPS.
{ "event": "message.received",
"data": { "id": 905, "from": "919812345678", "name": "Ravi", "type": "text",
"text": "Is the offer still on?", "waba": "919000000001",
"received_at": "2026-11-02T10:41:00.000Z" },
"sent_at": "2026-11-02T10:41:01.000Z" }
{ "event": "message.status",
"data": { "id": 811, "client_ref": "order-1043", "to": "919812345678", "status": "delivered" },
"sent_at": "2026-11-02T10:30:04.000Z" }Check the signature
Every request has an X-Signature header: sha256= followed by the HMAC-SHA256 of the raw body, using the signing secret shown in your panel. Reject requests where it does not match.
const crypto = require("crypto");
// rawBody = the exact bytes you received, before JSON.parse
const expected = "sha256=" + crypto.createHmac("sha256", process.env.AISENDER_WEBHOOK_SECRET).update(rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-signature"] || ""));<?php
$raw = file_get_contents("php://input");
$expected = "sha256=" . hash_hmac("sha256", $raw, getenv("AISENDER_WEBHOOK_SECRET"));
if (!hash_equals($expected, $_SERVER["HTTP_X_SIGNATURE"] ?? "")) { http_response_code(401); exit; }
$event = json_decode($raw, true);Reply with any 2xx status within 10 seconds. If your server does not answer, we try again up to 3 times.
Errors
Errors return a JSON body with a clear message:
{ "error": { "status": 402, "message": "Not enough balance: needs ₹200.00, wallet has ₹50.00" } }| Code | Meaning |
|---|---|
400 | Something in the request is wrong: template name, variable count, number, or a text sent outside the 24-hour window. |
401 | API key missing, wrong or revoked. |
402 | Not enough wallet balance. Recharge and try again. |
403 | Sending is paused on the account, the number is paused, or the request came from an IP not allowed for this key. |
404 | Message not found. |
429 | Too many requests. Slow down and retry. |
500 | Something went wrong on our side. Retry with the same client_ref, so nothing is sent twice. |
Rate limits
- 20 requests per second per API key, with short bursts up to 40.
- Up to 1,000 recipients per bulk request.
- WhatsApp also has its own daily limit on how many customers your number can start chats with. It grows as you send good-quality messages. See WhatsApp messaging limits.
Billing
- The price of each message is taken from your prepaid wallet when it is queued, and is returned in
charged. - Failed messages are refunded to your wallet automatically.
- New accounts get 500 free messages for order updates, reminders and alerts. These are used before your wallet.
- Rates for each message type are shown in your panel. See WhatsApp API pricing in India.
Ready to integrate?
Sign up, connect your number and create an API key in minutes. Need help? Our team can help your developer.