Campaigns
هذا المحتوى غير متوفر بلغتك بعد.
A campaign sends one approved template to many people, paced against WhatsApp’s daily messaging allowance.
Creating and starting are separate calls. Building a recipient list takes several requests, and a create that also sent would make a half-uploaded list unrecoverable.
POST /v1/campaigns
Section titled “POST /v1/campaigns”Creates a campaign in draft. Nothing is sent.
Scope: campaigns:write · Accepts Idempotency-Key
{ "name": "Eid offer", "template": "eid_promo", "account": "Main"}| Field | Notes |
|---|---|
name | Required. For your own records. |
template | Template name. Or template_id. Must be APPROVED. |
account | Which number to send from. Defaults to the template’s, then the organization default. |
POST /v1/campaigns/{id}/recipients
Section titled “POST /v1/campaigns/{id}/recipients”Adds recipients. Call it repeatedly for a large list.
Scope: campaigns:write · Accepts Idempotency-Key
{ "recipients": [ { "to": "+966500000001", "name": "Sara", "parameters": { "name": "Sara", "code": "EID20" } }, { "to": "+966500000002", "name": "Omar", "parameters": { "name": "Omar", "code": "EID20" } } ]}Up to 1000 per call. Each entry needs to; parameters fills the
template’s variables for that person.
{ "added": 2, "total_recipients": 2, "campaign_id": "5d4c3b2a-..." }A batch is written in one transaction: if one entry is rejected, none of them
are stored, so the campaign never holds a list you believe it does not have.
The validation error names the offending index (recipients[41].to), because
nobody can find the bad row in a thousand otherwise.
Recipients can only be added while a campaign is draft or paused. Adding to
a running campaign would race the workers draining the queue, so whether the
new rows went out would depend on timing.
POST /v1/campaigns/{id}/start
Section titled “POST /v1/campaigns/{id}/start”Starts sending, or arms the campaign for later.
Scope: campaigns:write · Accepts Idempotency-Key
{ "send_mode": "spread", "scheduled_at": "2026-08-25T09:00:00Z" }Both fields are optional. With no body, it starts now with spread.
send_mode | Behaviour |
|---|---|
spread (default) | Recipients who do not fit inside today’s allowance are carried into following days. Nobody is dropped. |
today_only | Send as many as today’s allowance permits and skip the rest, counted in skipped. |
scheduled_at in the future arms the campaign; the processor picks it up when
the time arrives.
{ "status": "processing", "queued": 480, "skipped": 0 }{ "status": "scheduled", "scheduled_at": "2026-08-25T09:00:00Z" }queued is how many were handed to the workers now, not how many will be sent
in total. Under spread, the rest follow on later days.
POST /v1/campaigns/{id}/pause
Section titled “POST /v1/campaigns/{id}/pause”Scope: campaigns:write
Stops a processing or scheduled campaign. Recipients already handed to the
workers still go out; the rest stay pending until you start it again.
GET /v1/campaigns/{id}
Section titled “GET /v1/campaigns/{id}”Scope: campaigns:read
{ "id": "5d4c3b2a-...", "name": "Eid offer", "status": "processing", "template_id": "7c6b5a4d-...", "template_name": "eid_promo", "account": "Main", "recipients": { "total": 1200, "sent": 480, "delivered": 421, "read": 190, "failed": 3, "skipped": 0 }, "send_mode": "spread", "started_at": "2026-08-20T09:00:00Z", "resumes_at": "2026-08-21T00:05:00Z", "throttle_reason": "Daily WhatsApp messaging limit reached", "created_at": "2026-08-19T16:20:00Z"}Statuses
Section titled “Statuses”| Status | Meaning |
|---|---|
draft | Created, not started |
scheduled | Armed for scheduled_at |
processing | Sending |
paused | Stopped by a person |
completed | Every recipient dealt with |
failed | Could not run |
resumes_at and throttle_reason
Section titled “resumes_at and throttle_reason”Set when a campaign parked itself against the daily allowance. Without them a stalled campaign looks broken; with them it is visibly waiting, and the time it resumes is stated.
GET /v1/campaigns
Section titled “GET /v1/campaigns”Scope: campaigns:read · Filter with status, paginate with limit and cursor.
Knowing when it finished
Section titled “Knowing when it finished”Poll GET /v1/campaigns/{id}, or subscribe to campaign.completed and have
the final counts delivered to you. See Event reference.