🎁 Offer: Sign up and get 500 WhatsApp messages free.Claim now
AiSender

Home / API documentation

AiSender WhatsApp API documentation

Send WhatsApp template messages, OTPs and order updates from your website, app or CRM, and get delivery reports and replies on your webhook. Simple REST API with JSON.

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.

Base URL
https://whatsapp.aisender.io/api/v1

Before 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:

Headers
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 sendWe use
+91 98123-45678919812345678
09812345678919812345678
9812345678919812345678
+971 50 123 4567971501234567

Send a template message

POST/messages

Send an approved template to one number. Use this for OTPs, order updates, reminders and offers.

FieldTypeDescription
to requiredstringRecipient's WhatsApp number with country code.
template.name requiredstringName of an approved template, exactly as in the panel.
template.languagestringTemplate language code, e.g. en, hi. Needed if the template exists in more than one language.
template.paramsarrayValues for the template variables {{1}}, {{2}}… in order. The count must match the template.
client_refstringYour own ID, up to 100 characters (e.g. order number). Sending the same client_ref again never sends twice.
wabastringWhich of your connected numbers to send from, if you have more than one.
cURL
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"}'
Node.js
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
<?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);
Python
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.

JSON
{
  "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

POST/messages

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

FieldTypeDescription
recipients requiredarrayList of { "to": "…", "params": [ … ] }. A plain list of numbers also works if the template has no variables.
template requiredobjectname and optional language, as above.
campaignstringCampaign name shown in reports, up to 120 characters.
wabastringWhich connected number to send from.
cURL
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%"]}
       ]}'
Response
{
  "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

POST/messages

Send 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
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

GET/messages/{id}

Get one message and its latest status.

Response
{
  "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"
}
StatusMeaning
queuedAccepted and waiting to be sent (or scheduled).
sentHanded to WhatsApp.
deliveredReached the customer's phone.
readThe customer opened it (only if they have read receipts on).
failedCould not be delivered. See error. The price is refunded to your wallet.
cancelledA scheduled campaign was cancelled before sending. Refunded.

List templates

GET/templates

Returns your templates with the number of variables each one needs. Add ?waba= to choose a connected number.

Response
{
  "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

GET/balance
Response
{ "balance": 941.8, "currency": "INR", "status": "active" }

status is blocked when sending is paused, for example when the wallet is empty.

Incoming messages

GET/inbox?after_id=0&limit=100

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

Response
{
  "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.

Webhook body
{ "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.

Node.js
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
<?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:

Response
{ "error": { "status": 402, "message": "Not enough balance: needs ₹200.00, wallet has ₹50.00" } }
CodeMeaning
400Something in the request is wrong: template name, variable count, number, or a text sent outside the 24-hour window.
401API key missing, wrong or revoked.
402Not enough wallet balance. Recharge and try again.
403Sending is paused on the account, the number is paused, or the request came from an IP not allowed for this key.
404Message not found.
429Too many requests. Slow down and retry.
500Something 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.

Get your API key