Skip to content
View as Markdown
ReferenceCampaigns

Campaigns

Send bulk messages to multiple contacts

DevelopersUpdated

Campaigns allow you to send bulk WhatsApp messages to multiple contacts using approved templates. The API handles rate limiting and delivery tracking automatically.

Retrieve all campaigns.

GET/api/campaigns
Parameter Type Description
page integer Page number (default: 1)
limit integer Page size, 1–100. Default 50.
status string Filter by status
whatsapp_account string Filter by WhatsApp account name
search string Case-insensitive match on the campaign name
from / to string YYYY-MM-DD range filter on created_at (to is inclusive of the whole day)

Ordered by created_at DESC.

{
"status": "success",
"data": {
"campaigns": [
{
"id": "uuid",
"name": "Holiday Promotion",
"whatsapp_account": "Main Business",
"template_id": "uuid",
"template_name": "holiday_offer",
"status": "completed",
"total_recipients": 1000,
"sent_count": 1000,
"delivered_count": 950,
"read_count": 500,
"failed_count": 50,
"scheduled_at": "2024-01-01T10:00:00Z",
"started_at": "2024-01-01T10:00:05Z",
"completed_at": "2024-01-01T10:30:00Z",
"created_by_name": "Jane Admin",
"updated_by_name": "Jane Admin",
"created_at": "2024-01-01T09:00:00Z",
"updated_at": "2024-01-01T10:30:00Z"
}
],
"total": 25,
"page": 1,
"limit": 50,
"summary": {
"campaigns": 25,
"sending": 1,
"sent": 18400,
"delivered": 17650,
"read": 11200,
"replied": 940,
"failed": 310
}
}
}

summary adds up every campaign the filters match, not just this page: how many there are, how many are sending now (queued or processing), and their message counts.

Retrieve a single campaign with detailed progress.

GET/api/campaigns/{id}
{
"status": "success",
"data": {
"id": "uuid",
"name": "Holiday Promotion",
"template_id": "uuid",
"template_name": "holiday_offer",
"whatsapp_account": "Main Business",
"status": "processing",
"total_recipients": 1000,
"sent_count": 450,
"delivered_count": 400,
"read_count": 100,
"failed_count": 10,
"scheduled_at": null,
"started_at": "2024-01-01T10:00:05Z",
"created_at": "2024-01-01T09:00:00Z",
"updated_at": "2024-01-01T10:05:00Z"
}
}

Create a new campaign. Per-recipient template values are supplied when you import recipients, not on the campaign itself — there is no variable_mapping field.

POST/api/campaigns
{
"name": "New Year Sale",
"whatsapp_account": "Main Business",
"template_id": "uuid",
"header_media_id": "",
"scheduled_at": "2024-01-01T00:00:00Z"
}
Field Type Required Description
name string Yes Campaign name
whatsapp_account string Yes Name of the sending WhatsApp account
template_id uuid Yes Template to send
header_media_id string No Meta media ID for a media-header template — upload it first with POST /api/campaigns/{id}/media
scheduled_at string No ISO-8601 time to start the campaign
{
"status": "success",
"data": {
"id": "uuid",
"name": "New Year Sale",
"status": "draft",
"created_at": "2024-01-01T00:00:00Z"
}
}

Update a draft campaign.

PUT/api/campaigns/{id}

Delete a campaign.

DELETE/api/campaigns/{id}

Import recipients from a CSV or JSON file.

POST/api/campaigns/{id}/recipients/import
{
"recipients": [
{
"phone_number": "+1234567890",
"recipient_name": "John Doe",
"template_params": { "customer_name": "John", "coupon": "SAVE20" },
"header_params": { "season": "Summer" }
},
{
"phone_number": "+0987654321",
"recipient_name": "Jane Smith",
"template_params": { "customer_name": "Jane", "coupon": "SAVE15" },
"header_params": { "season": "Winter" }
}
]
}

header_params is only needed for templates with a TEXT header variable (Meta allows at most one). It’s kept separate from template_params so a positional header {{1}} doesn’t collide with body {{1}}. Omit the field entirely for templates without a header variable.

{
"status": "success",
"data": {
"message": "Recipients added successfully",
"added_count": 2,
"total_recipients": 1002
}
}

added_count is the number of recipients in this request; total_recipients is the campaign’s new total.

Get all recipients of a campaign with their per-recipient delivery status. This endpoint returns the full recipient list for the campaign — it is not paginated.

GET/api/campaigns/{id}/recipients

The recipients array holds every recipient; total is the count.

{
"status": "success",
"data": {
"recipients": [
{
"id": "uuid",
"campaign_id": "uuid",
"phone_number": "1234567890",
"recipient_name": "John Doe",
"status": "delivered",
"template_params": { "customer_name": "John", "coupon": "SAVE20" },
"header_params": { "season": "Summer" },
"whatsapp_message_id": "wamid.xxx",
"error_message": "",
"sent_at": "2024-01-01T10:00:10Z",
"delivered_at": "2024-01-01T10:00:15Z",
"read_at": "2024-01-01T10:05:00Z"
}
],
"total": 1000
}
}
Field Type Description
phone_number string Recipient phone number
recipient_name string Recipient display name
status string pending, sent, delivered, read, or failed
template_params object Body parameter values used for this recipient
header_params object TEXT-header parameter values (if any)
whatsapp_message_id string Meta message ID once sent
error_message string Failure reason when status is failed
sent_at / delivered_at / read_at string Delivery timestamps (null until reached)

Remove a single recipient from a campaign. Only allowed while the campaign is in draft status.

DELETE/api/campaigns/{id}/recipients/{recipientId}
{
"status": "success",
"data": {
"message": "Recipient deleted successfully"
}
}

Upload the media asset for a campaign whose template has a media header (image, video, or document). Allowed only for draft campaigns whose template has a non-TEXT header. The file is uploaded to WhatsApp and stored locally for preview.

POST/api/campaigns/{id}/media

Send the request as multipart/form-data with a file field (max 16 MB).

Terminal window
curl -X POST "http://your-server:8080/api/campaigns/{id}/media" \
-H "X-API-Key: soosh_your_api_key" \
{
"status": "success",
"data": {
"media_id": "meta-media-id",
"filename": "banner.jpg",
"mime_type": "image/jpeg",
"local_path": "campaigns/uuid.jpg",
"message": "Media uploaded successfully"
}
}

Serve the previously uploaded campaign media file (used for previews). Returns the raw media bytes, not a JSON envelope.

GET/api/campaigns/{id}/media

Begin sending messages.

POST/api/campaigns/{id}/start

Pause a running campaign.

POST/api/campaigns/{id}/pause

Cancel a campaign (cannot be resumed).

POST/api/campaigns/{id}/cancel

Re-queue every recipient whose delivery failed. Allowed only on completed, paused, or failed campaigns. Failed recipients are reset to pending, the campaign moves back to processing, and the messages are re-enqueued.

POST/api/campaigns/{id}/retry-failed
{
"status": "success",
"data": {
"message": "Retrying failed messages",
"retry_count": 12,
"status": "processing"
}
}

Fetch live progress for a campaign. This returns the same payload as Get Campaign (status plus sent/delivered/read/failed counts) and is convenient for polling while a campaign is sending.

GET/api/campaigns/{id}/progress
Status Description
draft Campaign created, not yet started
scheduled Campaign scheduled for future sending
queued Recipients have been pushed onto the Redis job queue
processing Workers are actively sending messages
paused Campaign is paused. When the system paused it (such as at the monthly broadcast limit), paused_reason says why
completed All messages have been processed
cancelled Campaign was cancelled
failed The campaign could not be processed

A super administrator can give a workspace a monthly limit on campaign messages (the broadcast_messages_per_month workspace limit). Every campaign message sent counts, and a retried one counts again; a send that fails doesn’t count. Months are calendar months in UTC.

  • When the month is used up, running campaigns pause with paused_reason set, and their unsent recipients stay pending. Resume them next month, or once the limit is raised.
  • Start and Retry Failed answer 403 with the reason while the month is used up.
GET/api/campaigns/usage
{
"status": "success",
"data": { "used": 8200, "limit": 10000, "period": "2026-10", "resets_at": "2026-11-01T00:00:00Z" }
}

limit is null when the workspace has no monthly limit; messages are counted either way.

Sends are dispatched through a Redis Streams job queue and consumed by worker processes. Throughput is governed by how many workers you run (-workers=N on server or worker), not by a built-in per-second throttle — Soosh does not shape traffic to your Meta messaging tier. If you exceed your tier, Meta rejects the sends and the affected recipients are marked failed; use Retry Failed once your limits allow.