WhatsApp API integration with AiSender takes a single authenticated HTTP call: you send a Meta-approved template name, a phone number and the variable values, and the message is queued and charged from your prepaid wallet. This guide covers authentication, single and bulk sends, status checks, webhooks, idempotency with client_ref, error handling and best practices, with curl and Node.js examples.
AiSender is built on the official WhatsApp Business Platform (Cloud API). If you're new to the platform, start with what is WhatsApp Business API.
Before you start
You need three things:
- A connected WhatsApp number. Connect it from the AiSender panel with Continue with Facebook; it takes about 10 minutes.
- At least one approved template. Business-initiated messages must use a template Meta has approved. See WhatsApp template message approval.
- An API key. Create one in the panel's API section. It is shown only once, so store it in a secret manager or environment variable, never in front-end code.
All requests go to the base URL https://whatsapp.aisender.io and send the key as a Bearer token:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
WhatsApp API integration endpoints at a glance
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/messages | Send one template message, or up to 1,000 in one request |
| GET | /api/v1/messages/{id} | Get the status of a message |
| GET | /api/v1/templates | List your approved templates and their variables |
| GET | /api/v1/balance | Check your wallet balance |
| GET | /api/v1/inbox?after_id=123&limit=100 | Poll incoming replies |
See the API docs in your panel for full details of every field.
Step 1: List your approved templates
Check the exact template name, language and number of variables before sending:
curl https://whatsapp.aisender.io/api/v1/templates \
-H "Authorization: Bearer $AISENDER_KEY"
Variables are positional: the first value in params fills {{1}}, the second fills {{2}}, and so on.
Step 2: Send a single message
Phone numbers use the full international format without a plus sign, for example 919876543210 for an Indian number.
curl -X POST https://whatsapp.aisender.io/api/v1/messages \
-H "Authorization: Bearer $AISENDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"template": { "name": "order_update", "language": "en", "params": ["Ravi", "#4521"] },
"client_ref": "order-4521-shipped"
}'
The same call in Node.js (18+ has fetch built in):
const BASE = 'https://whatsapp.aisender.io';
async function sendOrderUpdate(phone, name, orderNo) {
const res = await fetch(`${BASE}/api/v1/messages`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.AISENDER_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: phone,
template: { name: 'order_update', language: 'en', params: [name, orderNo] },
client_ref: `order-${orderNo}-shipped`,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`AiSender ${res.status}: ${JSON.stringify(data)}`);
return data; // { queued, ids, charged, balance }
}
A successful response tells you how many messages were queued, their ids, how much was charged and your remaining balance:
{ "queued": 1, "ids": [811], "charged": 0.89, "balance": 1243.11 }
The values above are illustrative; the actual charge depends on the template category and current rates.
Step 3: Send in bulk (up to 1,000 per request)
For campaigns, send a recipients array with per-recipient params and a campaign name, so the send shows up in your panel reports:
const body = {
campaign: 'Diwali offer',
template: { name: 'diwali_offer', language: 'en' },
recipients: [
{ to: '919876543210', params: ['Ravi'] },
{ to: '919812345678', params: ['Priya'] },
],
};
For lists larger than 1,000, split them into chunks and send the chunks one after another. Check your balance with GET /api/v1/balance before a large send so you don't stop halfway.
Step 4: Track delivery with webhooks
Polling every message is wasteful. Instead, add an HTTPS webhook URL in the panel. AiSender will POST delivery updates (sent, delivered, read, failed) and incoming customer replies to it. A status event looks like this:
{ "event": "message.status", "data": { "id": 811, "client_ref": "order-4521-shipped", "to": "919876543210", "status": "delivered" }, "sent_at": "..." }
Each webhook request is signed with your webhook secret. Verify the signature before trusting the data. A minimal Express handler:
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.post('/whatsapp/webhook', express.raw({ type: '*/*' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.AISENDER_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const got = req.get('X-Signature') || '';
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge fast, process after
const evt = JSON.parse(req.body);
queueForProcessing(evt); // your own job queue
});
Respond quickly with a 200 and do slow work (database writes, CRM updates) in the background. Handle events idempotently, because the same event can arrive more than once.
If you can't expose a public URL, poll replies instead with GET /api/v1/inbox?after_id=123&limit=100, storing the last id you processed and passing it as after_id next time. For one-off checks, GET /api/v1/messages/{id} returns a single message's status.
Idempotency with client_ref
Network timeouts happen. If your request times out, you don't know whether the message was queued, and a blind retry could send it twice. Pass a client_ref on single sends: a unique string from your own system, such as order-4521-shipped. Sending the same client_ref again will not send a second message; the API returns the original result instead. The client_ref also comes back in status webhooks, so you can match updates to your own records without storing AiSender ids.
Good client_ref values are deterministic: build them from your order id plus the event, not from a random value generated at retry time.
Error handling
Check the HTTP status on every call. The common ones:
| Status | Meaning | What to do |
|---|---|---|
| 202 | Queued | Store the ids; wait for webhooks |
| 400 | Bad request (missing field, wrong template or params) | Fix the payload; don't retry as-is |
| 401 | Invalid or revoked API key | Check the key; alert your team |
| 402 | Not enough wallet balance | Top up, then retry |
| 403 | Blocked, or request from an IP not allowed for this key | Check key IP settings |
| 429 | Too many requests | Back off and retry with a delay |
| 5xx | Temporary server problem | Retry with exponential backoff and the same client_ref |
The exact list and rate limits are in the API docs in your panel. Failed messages are refunded to your wallet automatically, so a "failed" webhook doesn't mean you lost the money.
WhatsApp API integration best practices
- Keep API keys on the server and restrict each key to your server's IP addresses where the panel allows it.
- Use one key per system (website, CRM, billing) so you can revoke one without breaking the others.
- Validate and normalise phone numbers to country code + number before sending.
- Send only to customers who opted in; see WhatsApp opt-in best practices.
- Use
client_reffor every transactional send and retry only with the same value. - Verify webhook signatures and process events idempotently.
- Check
/api/v1/templatesat startup so a renamed or paused template fails loudly in testing, not silently in production. - Watch
balancein responses and alert before it runs low. - Log request ids and responses for support and debugging.
FAQ
Can I send free-form text messages through the API?
Business-initiated messages must use an approved template. Free-form replies are only allowed inside the 24-hour customer service window, which your team can handle from the shared Live Chat inbox. See the API docs in your panel for what the API supports.
How many messages can I send per request?
One message with to, or up to 1,000 recipients with the recipients array. Your overall daily reach also depends on your number's Meta messaging limit tier.
Do I need webhooks?
No, but they're recommended. Without them, you can poll GET /api/v1/messages/{id} for status and GET /api/v1/inbox for replies.
What happens if a message fails?
You'll get a failed status via webhook or the status endpoint, and the charge is refunded to your wallet automatically.
Ready to build? Sign up at AiSender (prepaid wallet, no monthly fee), connect your number, generate an API key and send your first template in minutes.
Try AiSender for your business
WhatsApp Business API, team inbox and AI chatbot. No monthly fee.