WhatsApp API Quickstart
Overview
The Cliqtel WhatsApp API lets you send and receive WhatsApp Business messages programmatically — text, templates, media, location, and interactive messages — all over a single REST endpoint. Every request is authenticated with a scoped API key and returns JSON.
All WhatsApp endpoints live under one base URL:
https://cliqtel.com/api/v1/whatsapp
By the end of this guide you'll have sent a live message and confirmed its delivery. The four steps are: create an API key, find your account ID, send a message, and track its status.
Prerequisites
- A WhatsApp Business Account connected to Cliqtel — see the WhatsApp setup guide to connect your WABA and register a number.
- A Cliqtel account with access to the Dashboard (to create API keys).
- A tool for making HTTP requests — the examples below use Node.js (
fetch), Python (requests), and cURL.
Authentication & Scopes
Every request must include your API key as a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
API keys carry scopes that determine what they can do. For WhatsApp there are two:
| Scope | Grants |
|---|---|
whatsapp:read | List messages, accounts, and templates |
whatsapp:write | Send messages and manage templates |
To follow this quickstart you'll need both scopes on your key. All request and response bodies are JSON.
Step 1 — Create an API Key
In the Cliqtel portal, go to Dashboard → API Keys and create a new key. When prompted for scopes, grant both:
whatsapp:readwhatsapp:write
Step 2 — Find Your Account ID
Each send needs an account_id — the numeric id of the WhatsApp Business Account you're sending from. List your connected accounts with a GET request:
curl https://cliqtel.com/api/v1/whatsapp/accounts \ -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch('https://cliqtel.com/api/v1/whatsapp/accounts', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
});
const accounts = await res.json();
console.log(accounts); // find the numeric "id" you want to send fromimport requests
res = requests.get(
'https://cliqtel.com/api/v1/whatsapp/accounts',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
)
print(res.json()) # find the numeric "id" you want to send fromNote the numeric id from the account you want to use — that's the account_id you'll pass on every send.
Step 3 — Send Your First Message
Messages are sent with POST /api/v1/whatsapp/messages. The type field decides what you're sending. There are two ways to start:
- Text (
type: "text") — free-form messages, only allowed inside an open 24-hour customer service window. - Template (
type: "template") — a pre-approved template, used to start a conversation when no window is open.
Send a text message
Use this inside an open 24-hour window (for example, replying to a customer who just messaged you).
curl -X POST https://cliqtel.com/api/v1/whatsapp/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_id": 1,
"to": "+31611398058",
"type": "text",
"text": { "body": "Thanks for reaching out! How can we help?" }
}'const res = await fetch('https://cliqtel.com/api/v1/whatsapp/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_id: 1, // from Step 2
to: '+31611398058',
type: 'text',
text: { body: 'Thanks for reaching out! How can we help?' },
}),
});
const data = await res.json();
console.log(data.id, data.status); // message ID, "accepted"import requests
res = requests.post(
'https://cliqtel.com/api/v1/whatsapp/messages',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={
'account_id': 1, # from Step 2
'to': '+31611398058',
'type': 'text',
'text': {'body': 'Thanks for reaching out! How can we help?'},
},
)
print(res.json()['id'], res.json()['status'])A successful send returns a JSON body with the new message's ID and its initial status:
{
"id": 90210,
"status": "accepted"
}Send a template message
Use a template to start a new conversation (no open window) or to re-engage a customer. The template must already be approved in your WhatsApp account, and you supply its name, language.code, and any components the template defines.
curl -X POST https://cliqtel.com/api/v1/whatsapp/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_id": 1,
"to": "+31611398058",
"type": "template",
"template": {
"name": "order_update",
"language": { "code": "en" },
"components": [{
"type": "body",
"parameters": [{ "type": "text", "text": "ORD-4821" }]
}]
}
}'const res = await fetch('https://cliqtel.com/api/v1/whatsapp/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_id: 1,
to: '+31611398058',
type: 'template',
template: {
name: 'order_update',
language: { code: 'en' },
components: [{
type: 'body',
parameters: [{ type: 'text', text: 'ORD-4821' }],
}],
},
}),
});
const data = await res.json();
console.log(data.id, data.status); // message ID, "accepted"import requests
res = requests.post(
'https://cliqtel.com/api/v1/whatsapp/messages',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={
'account_id': 1,
'to': '+31611398058',
'type': 'template',
'template': {
'name': 'order_update',
'language': {'code': 'en'},
'components': [{
'type': 'body',
'parameters': [{'type': 'text', 'text': 'ORD-4821'}],
}],
},
},
)
print(res.json()['id'], res.json()['status'])media (images, documents, audio, video), location, and interactive messages (buttons and lists). See Rich WhatsApp Messages for those payloads.
Step 4 — Track Status
Every WhatsApp message moves through a lifecycle:
accepted -> sent -> delivered -> read
\-> failedYou can poll for status, or receive it via webhooks.
List and filter messages
Fetch recent messages with GET /api/v1/whatsapp/messages (supports filtering):
curl "https://cliqtel.com/api/v1/whatsapp/messages?account_id=1" \ -H "Authorization: Bearer YOUR_API_KEY"
Fetch a single message
Retrieve one message (and its current status) by ID with GET /api/v1/whatsapp/messages/{id}:
const res = await fetch('https://cliqtel.com/api/v1/whatsapp/messages/90210', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
});
const msg = await res.json();
console.log(msg.status); // "sent", "delivered", "read", or "failed"import requests
res = requests.get(
'https://cliqtel.com/api/v1/whatsapp/messages/90210',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
)
print(res.json()['status']) # "sent", "delivered", "read", or "failed"Delivery webhooks
Instead of polling, pass a status_callback_url on a send to receive delivery updates as they happen. Cliqtel will POST status changes (sent, delivered, read, failed) to your endpoint.
await fetch('https://cliqtel.com/api/v1/whatsapp/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_id: 1,
to: '+31611398058',
type: 'text',
text: { body: 'Your order has shipped.' },
status_callback_url: 'https://your-app.com/webhooks/whatsapp',
}),
});{ "status": "accepted" }, you're now sending WhatsApp messages through Cliqtel. Everything from here is refinement.
Next Steps
- Rich WhatsApp Messages — media, location, and interactive button/list messages.
- WhatsApp Campaigns — send approved templates to many recipients at once.
- WhatsApp Setup Guide — connecting your WABA, registering numbers, and managing templates.
- Full API Reference — every endpoint, parameter, and response shape.
Rate Limits & Errors
The WhatsApp API is rate limited to 60 requests per minute per API key. If you exceed it, requests are rejected until the window resets — batch or throttle high-volume sends accordingly.
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
403 | Key lacks the required scope (whatsapp:write for sends) |
422 | Invalid payload — e.g. free-form text outside the 24-hour window, or a missing template parameter |
429 | Rate limit exceeded (60 requests/minute) |
4xx means the API never accepted your request. A message that was accepted but later shows status failed means WhatsApp couldn't deliver it — check the message record for the failure reason.
FAQ
What's the difference between whatsapp:read and whatsapp:write?
whatsapp:read lets a key list messages, accounts, and templates — useful for read-only dashboards. whatsapp:write is required to send messages and manage templates. To follow this quickstart, grant both.
Why do I need a template to send my first message?
WhatsApp only permits free-form text inside a 24-hour customer service window — which opens when the customer messages you first. To start a conversation, you must use an approved template. Once the customer replies, the window opens and you can send free-form text for the next 24 hours.
Where do I get the account_id?
Call GET /api/v1/whatsapp/accounts (Step 2). Each connected WhatsApp Business Account has a numeric id — use that value as account_id on every send.
How do I know when a message is delivered?
Either poll GET /api/v1/whatsapp/messages/{id} and read the status field, or pass a status_callback_url on the send to have Cliqtel push status changes to your webhook as they happen.
Can I send images, buttons, or locations?
Yes — the same POST /api/v1/whatsapp/messages endpoint supports media, location, and interactive types. See Rich WhatsApp Messages for the payloads, and the full API reference for every field.
Ready to build?
Explore rich message types and bulk campaigns, or dive into the complete API reference.