Audience Sends
Use this when your system knows who to message but does not hold their
WhatsApp numbers. whatbot looks each reference up among your contacts (the
source_ref you synced them with), sends to the ones it can reach, and tells you
what happened to every lead you asked for. No phone number is accepted, returned
or logged.
curl https://api.whatbot.in/public/v1/audience-sends \ -H "X-API-Key: wa_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "template_name": "site_visit_invite", "language": "en", "variables": ["Kshetra", {"field": "first_name", "default": "there"}], "source_refs": ["crm:lead:0b9f6f7e-2a61-4c1a-9d0e-3f1f7c2f9a11"], "idempotency_key": "zx-run-2026-09-26-001" }'| Field | Type | Required | Description |
|---|---|---|---|
| template_name | string | ✓ | An approved template (see List Templates). |
| language | string | ✓ | The template’s language exactly as approved, e.g. en or en_US. |
| variables | array | One value per placeholder, in the order /templates lists them (a website or copy-code button that takes a value counts as one). A string is sent as-is to everyone; {"field": "first_name", "default": "there"} is read from each contact. | |
| source_refs | string[] | one of | Up to 20,000 crm:lead:<uuid> references. Duplicates are ignored. |
| segment_ref | string | one of | A CRM segment, crm_seg:<id>, resolved from your CRM at send time. |
| idempotency_key | string | ✓ | 1–200 characters. Retrying with the same key never sends twice. |
| scheduled_at | string | A future ISO-8601 time. Omit to send now. | |
| from | string | Send from this number (phone number id or E.164). Defaults to your workspace default. | |
| header_media | object | The document a DOCUMENT-header template carries: {"type": "document", "link": "https://…", "filename": "Brochure.pdf"}. Required for (and only for) such a template, and only on a send to exactly one source_ref. See Sending a document. |
Give exactly one of source_refs or segment_ref.
Template shapes this send can’t use: a template with an IMAGE, VIDEO
or LOCATION header (check header_format in /templates), a DOCUMENT
header without header_media, one-time-code (authentication) templates,
and templates written with named placeholders ({{first_name}} rather than
{{1}}). All are refused with a 422 before anything is created.
Sending a document
Section titled “Sending a document”A template whose header is DOCUMENT (a brochure, a price list) can be sent to
one lead with header_media:
"source_refs": ["crm:lead:0b9f6f7e-2a61-4c1a-9d0e-3f1f7c2f9a11"],"header_media": { "type": "document", "link": "https://cdn.example.com/brochures/kshetra.pdf", "filename": "Kshetra Brochure.pdf"}linkmust be https and serve the file to an anonymous GET — a public CDN link or a presigned URL. whatbot fetches it once, when you call; every redirect must stay on https and on the public internet.- At most 25 MB.
filenamemust end in.pdf,.doc(x),.xls(x),.ppt(x)or.txt; it defaults to the link’s last path segment and is what the customer sees. A.pdfmust really be a PDF, and a web page (for example a sign-in page) is refused. - Retrying with the same
idempotency_keyreplays the send. The document is matched by the link’s host and path, so a re-signed presigned link for the same file is the same send; a different file is a 409. - A link that answers 4xx, or serves the wrong kind of file, is a 422. A link that times out or answers 5xx is a 502 — nothing was sent; retry the same key.
Retries. Re-send with the same idempotency_key whenever you didn’t get an
answer — including after a scheduled time has passed; you get the original send
back. If the answer is pending, the first request is still being built: retry
the same key a few seconds later. A key that stays pending belongs to a request
that failed part-way and never sent; use a new key.
Response
Section titled “Response”Every lead you asked for lands in exactly one bucket:
requested = queued + skipped_unknown_ref + skipped_no_phone + skipped_contact_opted_out + skipped_opted_out + skipped_missing_variable + merged_same_phone
| Field | Type | Description |
|---|---|---|
| job_id | string | null | Track it with GET /audience-sends/{job_id}. null when no lead had a reachable number; set (with nothing_to_send) when contacts were found but every one was skipped. |
| status | string | queued or scheduled (it will go); nothing_to_send (no lead can be messaged — including every one skipped for a missing variable); insufficient_balance (held — top up, retry the same key); pending (a retry while the first request with this key is still being created — retry the same key shortly); cancelled (cancelled in the dashboard). Only queued and scheduled will send. |
| requested | integer | Distinct leads asked for. |
| queued | integer | Messages that will be sent. |
| skipped_unknown_ref | integer | No contact with that reference in this workspace. |
| skipped_no_phone | integer | A contact, but no usable WhatsApp number. |
| skipped_contact_opted_out | integer | The contact is marked opted out. |
| skipped_opted_out | integer | The number is on your opt-out list (e.g. replied STOP). |
| skipped_missing_variable | integer | A {"field": …} variable had no value and no default. Never sent blank. |
| merged_same_phone | integer | A second lead on a number already in this send; it gets one message. |
| idempotent_replay | boolean | true when this answer is the original send for a reused key. |
{ "job_id": "5c1f3a0e-8d2b-4c55-9a51-0d6c2b7e9f10", "status": "queued", "requested": 1200, "queued": 1034, "skipped_unknown_ref": 41, "skipped_no_phone": 97, "skipped_contact_opted_out": 6, "skipped_opted_out": 12, "skipped_missing_variable": 0, "merged_same_phone": 10, "template_name": "site_visit_invite", "language": "en", "scheduled_at": null, "idempotent_replay": false}Errors
Section titled “Errors”| Status | Meaning |
|---|---|
| 404 | Your CRM returned not-found for segment_ref — the id is wrong, or your CRM doesn’t support segment sends yet (use source_refs). |
| 409 | idempotency_key was already used for a different send (template, variables, audience, schedule, number or document). |
| 422 | Invalid input: a reference that is not crm:lead:<uuid>, an unapproved template, the wrong number of variables, an image/video/location-header or one-time-code template, a document-header template without header_media (or header_media on any other template, or with more than one lead), a document link that is not https, not public, too large, the wrong type or answers 4xx, a past scheduled_at. |
| 424 | No WhatsApp number is connected to this workspace (code: whatsapp_not_connected), or the sending number was disconnected (reason: number_disconnected). Nothing was sent. Connect one, then retry with the same key. |
| 502 | Your CRM could not be reached to resolve segment_ref, or the header_media link timed out / answered 5xx, or WhatsApp refused the document upload. Nothing was sent. Retry. |
| 503 | The template could not be verified with WhatsApp, or CRM segments are not connected, or audience sends are not enabled on this deployment yet. Nothing was sent. Retry. |
Track delivery
Section titled “Track delivery”curl https://api.whatbot.in/public/v1/audience-sends/5c1f3a0e-8d2b-4c55-9a51-0d6c2b7e9f10 \ -H "X-API-Key: wa_live_xxx"{ "job_id": "5c1f3a0e-8d2b-4c55-9a51-0d6c2b7e9f10", "status": "sending", "counts": { "target": 1034, "queued": 1034, "sent": 900, "delivered": 850, "read": 400, "failed": 12, "not_in_whatsapp": 9, "frequency_limited": 2, "skipped": 18, "replied": 57 }, "audience": { "requested": 1200, "queued": 1034, "skipped_no_phone": 97, "…": "…" }}sent ⊇ delivered ⊇ read. failed includes not_in_whatsapp (the number has no
WhatsApp account) and frequency_limited (Meta’s marketing cap). skipped were
never attempted. replied counts recipients who wrote back: one per recipient,
for their first message within 30 days, credited to the latest send that reached
that number.
From an AI agent (MCP)
Section titled “From an AI agent (MCP)”The same send is available on the hosted MCP server (connection details under
Settings → MCP in the dashboard) as
send_template_to_audience (needs a key with MCP write access), alongside the
read tools get_send_job and list_approved_templates.