Skip to content

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
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"
}'
FieldTypeRequiredDescription
template_namestring✓An approved template (see List Templates).
languagestring✓The template’s language exactly as approved, e.g. en or en_US.
variablesarrayOne 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_refsstring[]one ofUp to 20,000 crm:lead:<uuid> references. Duplicates are ignored.
segment_refstringone ofA CRM segment, crm_seg:<id>, resolved from your CRM at send time.
idempotency_keystring✓1–200 characters. Retrying with the same key never sends twice.
scheduled_atstringA future ISO-8601 time. Omit to send now.
fromstringSend from this number (phone number id or E.164). Defaults to your workspace default.
header_mediaobjectThe 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.

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"
}
  • link must 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. filename must 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 .pdf must really be a PDF, and a web page (for example a sign-in page) is refused.
  • Retrying with the same idempotency_key replays 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.

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

FieldTypeDescription
job_idstring | nullTrack 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.
statusstringqueued 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.
requestedintegerDistinct leads asked for.
queuedintegerMessages that will be sent.
skipped_unknown_refintegerNo contact with that reference in this workspace.
skipped_no_phoneintegerA contact, but no usable WhatsApp number.
skipped_contact_opted_outintegerThe contact is marked opted out.
skipped_opted_outintegerThe number is on your opt-out list (e.g. replied STOP).
skipped_missing_variableintegerA {"field": …} variable had no value and no default. Never sent blank.
merged_same_phoneintegerA second lead on a number already in this send; it gets one message.
idempotent_replaybooleantrue when this answer is the original send for a reused key.
200
{
"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
}
StatusMeaning
404Your CRM returned not-found for segment_ref — the id is wrong, or your CRM doesn’t support segment sends yet (use source_refs).
409idempotency_key was already used for a different send (template, variables, audience, schedule, number or document).
422Invalid 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.
424No 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.
502Your 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.
503The 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.
cURL
curl https://api.whatbot.in/public/v1/audience-sends/5c1f3a0e-8d2b-4c55-9a51-0d6c2b7e9f10 \
-H "X-API-Key: wa_live_xxx"
200
{
"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.

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.