DEVELOPERS
ConnectBatch API
Send WhatsApp messages and read replies from your own systems: an ERP, CRM, billing tool, script or AI agent.
Everything you do through the API shows up in ConnectBatch as usual: delivery reports, analytics and the Inbox.
Authentication
The API and webhooks are part of the Business plan. Create a key in ConnectBatch under Settings → API keys. It's shown once, so copy it then. Send it with every request:
Authorization: Bearer cb_live_…- A key works only for the workspace it was created in.
- Revoke a key in Settings and it stops working immediately.
- Keep keys on your server. Never put one in a website or mobile app.
Basics
- Base URL:
https://connectbatch.tech/api/v1 - Format: JSON in, JSON out. Times are ISO 8601, for example
2026-11-15T04:30:00Z. - Errors: errors return
{ "error": "What went wrong, in plain words" }. Validation errors (422) also includeissues. Status codes:401: bad key402: not enough credits (for a business that pays from credits): the error says how much the message needs and how much is available404: not found in your workspace409: conflict422: invalid request429: rate limited (seeRetry-After)
- Rate limit: 300 requests a minute per key.
- Retries: send an
Idempotency-Keyheader with everyPOSTthat sends something. Use a value unique to that action, such asinvoice-1042. If you retry with the same key, you get the first response back and nothing is sent twice. Keys are remembered for 24 hours. - Machine-readable description:
GET /openapi.json(no key needed) describes every endpoint, request and event in OpenAPI 3.1. Import it into Postman, generate a client library from it, or give it to an AI agent so it can call the API correctly.
How sending works
The API is for messaging: send WhatsApp templates to people, upload files, read and answer replies, and get events at your own URLs. Campaigns, groups and schedules are ConnectBatch app features and aren't part of the API.
Each message uses an approved WhatsApp template. WhatsApp only allows templates when you start a conversation; within 24 hours of someone's message you can also reply freely (see Inbox and replies). Template variables are filled in this order:
- From
variables: plain text the same for everyone, or{ "kind": "column", "column": "Amount" }to use each contact's own field. - Automatically from a contact field with the same name: for example, a field
"Amount"fills[Amount], and the contact's name fills[Name].
In ConnectBatch, everything sent through the API appears under Direct messages, with delivery reports, replies in the Inbox and analytics.
Endpoints
Templates
GET /templates lists your templates. Only templates with "canSend": true (approved by WhatsApp) can be sent.
{ "templates": [{ "id": "…", "slug": "fee-reminder", "name": "Fee reminder", "status": "APPROVED", "canSend": true,
"variables": ["Name", "Amount", "Due date"], "body": "Hi [Name], …", "category": "UTILITY" }] }Send a message
POST /messages sends an approved template to people, now or at sendAt:
curl -X POST https://connectbatch.tech/api/v1/messages \
-H "Authorization: Bearer cb_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fee-reminder-1042" \
-d '{
"template": "fee-reminder",
"variables": { "School": "Green Valley School" },
"contacts": [
{ "phone": "+91 98200 12345", "name": "Aarav", "fields": { "Amount": "₹4,500", "Due date": "10 Nov" } }
]
}'- Who gets it:
contacts(phone, name, fields), ortowith a plain list of numbers. Write numbers with their country code; Indian numbers can leave it out. template: the template'sslug,id, or WhatsApp name.attachment: for a template with an image, video or document at the top: theattachmentfromPOST /media.sendAt: optional, for example"2026-11-09T04:30:00Z". Leave it out to send now.skipInvalid: optional. Whentrue, numbers that can't be WhatsApp numbers are skipped and listed in the response'sskipped, instead of refusing the whole request. Useful for large lists.- Response:
201with the new message:
{ "id": "…", "template": "Fee reminder", "status": "SENDING", "skipped": [],
"stats": { "total": 1, "queued": 1, "sent": 0, "delivered": 0, "read": 0, "failed": 0 } }Status and delivery reports
GET /messages/{messageId} returns a message's status and its delivery counts:
statusis one ofSCHEDULED,SENDING,SENT,FAILEDorCANCELLED.- The counts are
total,sent,delivered,readandfailed.
GET /messages/{messageId}/deliveries returns each person's status, with any failure reason and their button tap or reply.
- Filter with
?state=FAILED(orQUEUED,SENT,DELIVERED,READ). - Page with
?page=2(50 per page).
To be told instead of asking, subscribe to the message.* events (see Webhooks).
Cancel a scheduled message
DELETE /messages/{messageId} cancels a message that is still scheduled. It returns the message with status: "CANCELLED". A message that has already started sending can't be cancelled (409).
Upload a file
POST /media uploads an image, video or document to send. Send it as multipart/form-data with the file in a field called file, and optionally kind (image, video or document; by default it's worked out from the file type).
curl -X POST https://connectbatch.tech/api/v1/media \
-H "Authorization: Bearer cb_live_…" \
-F "file=@term1-report.pdf"{ "mediaId": "1234567890", "kind": "document", "filename": "term1-report.pdf", "size": 48213, "mime": "application/pdf",
"attachment": { "kind": "file", "mediaId": "1234567890", "filename": "term1-report.pdf", "size": 48213, "mime": "application/pdf" } }- Use
attachmentas a message'sattachment, for a template with an image, video or document header. - Use
mediaId,kindandfilenamein amediareply in a conversation. - Limits: images JPEG or PNG up to 5 MB, videos MP4 up to 16 MB, documents (PDF, Office, text) up to 100 MB.
- WhatsApp keeps uploaded files for 30 days, so upload close to when you send.
Inbox and replies
GET /inbox lists incoming messages, newest first.
- Filter with
?filter=needs_reply,?filter=unreador?q=search. - Page with
?page=2.
GET /conversations/{phone} returns everything to and from one number (URL-encode the + as %2B). window.open says whether a free-text reply is allowed right now.
POST /conversations/{phone} replies. Free text:
{ "text": "Hi Aarav, the receipt is on its way." }Or send something richer, with a type:
type | Fields | WhatsApp shows |
|---|---|---|
buttons | body, buttons (1–3 labels, up to 20 characters), optional footer | Your message with reply buttons |
list | body, button (menu label), rows (1–10 { title, description? }), optional footer | A menu of options |
media | kind (image, video or document), mediaId, filename, optional caption | A file. Upload it first with POST /media. |
location | latitude, longitude, optional name and address | A map pin |
contact | name, phone (with country code) | A contact card |
location_request | body | Your message with a "Send location" button |
form | formId, body, button (up to 20 characters) | Your message with a button that opens one of your published WhatsApp forms. Answers arrive as form.submitted events. |
payment | body, items (1–10 { name, amount, quantity? }, amounts in rupees) | A bill with a "Review and pay" button (when payments are turned on for your business). Payment changes arrive as payment.updated events. |
product | productId, optional note | A product from your catalogue: photo, name, price and a Buy button (when the catalogue is turned on). |
{ "type": "buttons", "body": "Shall we book the venue visit?", "buttons": ["Yes, book it", "Not now"] }A tap on a button or a list option arrives like any other incoming message, with the button's label as its text.
This only works within 24 hours of the person's last message, which is WhatsApp's rule. Outside that window, send a template instead. Replies show in the Inbox as sent by "API · <key name>".
Working through conversations (AI agents and helpdesks)
GET /conversationslists conversations, one per person, newest first, withunread,needsReplyand the last message. Filter with?filter=unreador?filter=needs_reply; search with?q=.POST /conversations/{phone}/readmarks their latest message read on WhatsApp (they see blue ticks) and in the Inbox. Send{ "typing": true }to also show "typing…" while you prepare the answer; it disappears when you reply, or after about 25 seconds.GET /inbound/{id}/mediadownloads a photo, document, voice note or video someone sent.idis the inbound message's id:data.idin themessage.receivedevent, or the item'sidin a conversation. WhatsApp keeps these files for about 30 days (410after that).PATCH /conversations/{phone}hands a conversation over:{ "needsReply": true }flags it for your team (it shows as needing a reply in the Inbox);falsemarks it handled.{ "assistant": "pause" }or"resume"turns ConnectBatch's own AI assistant off or on for this person, so it and your agent don't both answer. Replying through the API pauses it automatically.
A typical AI agent:
- Subscribe to
message.received. - When one arrives:
POST /conversations/{phone}/readwithtyping: true, thenGET /conversations/{phone}for the history (andGET /inbound/{id}/mediafor any photo or document). - Reply with
POST /conversations/{phone}(with anIdempotency-Key). Ifwindow.openis false, send an approved template withPOST /messagesinstead. - If it can't help,
PATCH /conversations/{phone}with{ "needsReply": true }so a person takes over.
Webhooks: events sent to your system
ConnectBatch can POST events to your own URL the moment they happen. Add one under Settings → Webhooks, or subscribe through the API (below).
| Event | When |
|---|---|
message.sent | WhatsApp accepted a message |
message.delivered | It reached the person's phone |
message.read | The person read it |
message.failed | It couldn't be sent or delivered (data.error says why) |
message.received | Someone sent you a message, tapped a button (data.button) or reacted |
payment.updated | A payment request was sent, paid, failed, cancelled or refunded (data.status) |
form.submitted | Someone filled in one of your WhatsApp forms (data.answers, each answer under its question) |
template.updated | WhatsApp approved, rejected, paused or disabled one of your templates (data.status, data.reason) |
In events, campaign says where a message came from: Direct messages for messages sent through the API, or the name of a campaign run in the ConnectBatch app.
Each event is a JSON POST:
{
"id": "evt_3f1c…",
"type": "message.read",
"createdAt": "2026-11-09T05:02:11.000Z",
"data": {
"messageId": "…", "deliveryId": "…", "whatsappMessageId": "wamid.…",
"campaign": { "id": "…", "name": "Fee reminders 2026" },
"template": "Fee reminder", "phone": "+919820012345", "name": "Aarav Shah",
"status": "read", "error": null, "at": "2026-11-09T05:02:10.000Z"
}
}message.received has a different data:
{ "id": "…", "from": "+919820012345", "name": "Aarav Shah", "type": "button", "text": "Paid", "button": "PAID",
"campaign": { "id": "…", "name": "…" }, "inReplyTo": { "id": "…", "title": "Fee reminder" }, "receivedAt": "…" }payment.updated (payments on WhatsApp, India) has the payment request as data. status is pending, paid, failed, canceled or refunded; method is upi, card, netbanking, wallet or manual (marked paid by your team); amounts are in rupees:
{ "id": "…", "referenceId": "CBX7K2M9QPL4", "phone": "+919820012345", "name": "Aarav Shah", "description": "Term 2 fees",
"items": [{ "name": "Tuition, term 2", "amountPaise": 1500000, "quantity": 1 }], "amount": 15000, "currency": "INR",
"mode": "gateway", "status": "paid", "method": "upi", "transactionId": "pay_Pq8Zt3XhY2", "refunded": 0, "paidAt": "…", "createdAt": "…" }- Answer quickly: reply with any
2xxstatus within 10 seconds. Do slow work after answering. - Retries: if your URL fails or doesn't answer, the event is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, then given up. After 20 events in a row are given up, the webhook is turned off. It can be turned back on in Settings.
- Unsubscribing: answering
410 Goneturns the webhook off immediately. - Duplicates: an event can arrive more than once, for example after a retry. Use
id(also in theConnectBatch-Event-Idheader) to ignore duplicates. - Your workspace only: you only receive your own workspace's events.
Checking the signature. Each request has a ConnectBatch-Signature: t=<unix seconds>,v1=<hex> header. v1 is HMAC-SHA256 of <t>.<raw request body> using the webhook's signing secret (whsec_…, shown in Settings). In Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const [, t, v1] = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "") ?? [];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 minutes
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Use the raw body exactly as received, before parsing the JSON.
form.submitted has the answers, with the message it was sent with (when there was one):
{ "id": "…", "form": { "id": "…", "name": "Sangeet RSVP" }, "phone": "+919820012345",
"answers": { "Will you attend?": "Yes", "Number of guests": "3", "Meal preference": ["Veg"] }, "files": 0,
"campaign": { "id": "…", "name": "Mehta wedding" }, "messageId": "…", "submittedAt": "…" }template.updated says what WhatsApp decided:
{ "id": "…", "title": "Fee reminder", "metaName": "fee_reminder", "language": "en", "category": "UTILITY",
"status": "approved", "previousStatus": "pending", "reason": null, "at": "…" }Managing webhooks through the API
Integrations such as Zapier subscribe this way:
| Request | What it does |
|---|---|
GET /me | Checks a key: returns the workspace and key name. Integrations use it to test a connection. |
POST /webhooks with { "url": "https://…", "event": "message.received" } | Subscribes. Use "events": [...] for several, or leave both out for all. The response has the webhook's id and secret. |
GET /webhooks | Lists the webhooks this key created |
DELETE /webhooks/{id} | Unsubscribes |
GET /webhooks/samples?event=message.read | Returns an example event, as a one-item list |
Revoking an API key turns off the webhooks it created.
Good to know
- Consent: only message people who agreed to hear from you. Complaints lower your number's quality rating, and WhatsApp can limit it.
- Meta's charges: WhatsApp charges for each delivered template message, billed by Meta to your WhatsApp account.
- Documents and images:
- Same file for everyone: set it on the template in ConnectBatch.
- Different file per person (report cards, bills): sending these through the API is coming next.
Questions? Write to us from your ConnectBatch account, or see the Privacy Policy.