Skip to content

Campaigns

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.

Terminal window
GET /api/campaigns
ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerPage size, 1–100. Default 50.
statusstringFilter by status
whatsapp_accountstringFilter by WhatsApp account name
searchstringCase-insensitive match on the campaign name
from / tostringYYYY-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
}
}

Retrieve a single campaign with detailed progress.

Terminal window
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.

Terminal window
POST /api/campaigns
{
"name": "New Year Sale",
"whatsapp_account": "Main Business",
"template_id": "uuid",
"header_media_id": "",
"scheduled_at": "2024-01-01T00:00:00Z"
}
FieldTypeRequiredDescription
namestringYesCampaign name
whatsapp_accountstringYesName of the sending WhatsApp account
template_iduuidYesTemplate to send
header_media_idstringNoMeta media ID for a media-header template — upload it first with POST /api/campaigns/{id}/media
scheduled_atstringNoISO-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.

Terminal window
PUT /api/campaigns/{id}

Delete a campaign.

Terminal window
DELETE /api/campaigns/{id}

Import recipients from a CSV or JSON file.

Terminal window
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.

Terminal window
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
}
}
FieldTypeDescription
phone_numberstringRecipient phone number
recipient_namestringRecipient display name
statusstringpending, sent, delivered, read, or failed
template_paramsobjectBody parameter values used for this recipient
header_paramsobjectTEXT-header parameter values (if any)
whatsapp_message_idstringMeta message ID once sent
error_messagestringFailure reason when status is failed
sent_at / delivered_at / read_atstringDelivery timestamps (null until reached)

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

Terminal window
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.

Terminal window
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: whm_your_api_key" \
-F "file=@banner.jpg"
{
"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.

Terminal window
GET /api/campaigns/{id}/media

Begin sending messages.

Terminal window
POST /api/campaigns/{id}/start

Pause a running campaign.

Terminal window
POST /api/campaigns/{id}/pause

Cancel a campaign (cannot be resumed).

Terminal window
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.

Terminal window
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.

Terminal window
GET /api/campaigns/{id}/progress
StatusDescription
draftCampaign created, not yet started
scheduledCampaign scheduled for future sending
queuedRecipients have been pushed onto the Redis job queue
processingWorkers are actively sending messages
pausedCampaign is paused
completedAll messages have been processed
cancelledCampaign was cancelled
failedThe campaign could not be processed

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 — Whatomate 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.