Create Campaign
Send an APPROVED WhatsApp template to a resolved audience — immediately or on a schedule. Sendiee validates the variable payload against the template before persisting the campaign, so a broken header / body / button mapping fails fast with a structured 422 CAMPAIGN_VARIABLES_INVALID.
Always preview first
POST /v2.0/campaigns/preview with the same audience block to confirm the count and sample before creating the campaign.Authentication
Bearer token + brandNumber. Plan limit max_campaigns_per_month is enforced.
Request Body
Top-level
| Parameter | Type | Description |
|---|---|---|
| brandNumber | string | Your WhatsApp Business phone number |
| campaignName | string | Free-form name shown in dashboard reports |
| templateId | string | Sendiee Template DB id (preferred) |
| templateName | string | Alternative to templateId — must be paired with language |
| language | string | Used with templateName to disambiguate locale variants |
| audience | object | Audience selector — see /docs/campaigns/preview for the full shape |
| variables | object | Variable payload — see below |
| schedule | object | { sendAt: ISO, timezone: IANA }. Omit to send immediately |
| notes | string | Free-form internal note (not sent to recipients) |
| dryRun | boolean | When true, validates the payload, resolves the audience, and returns totalContacts plus a per-contact resolvedSample WITHOUT creating the campaign, consuming quota, or sending. Alias: validateOnly |
variables
variables
| Parameter | Type | Description |
|---|---|---|
| variables.header.mediaUrl | string | Required when the template header is IMAGE/VIDEO/DOCUMENT |
| variables.header.text | string | { field } | TEXT header value. A string is sent to everyone; a { field } reference is resolved per contact |
| variables.body | (string | { field, default? })[] | Positional values for {{1}}, {{2}}…. Each item is a static string OR a per-contact { field } reference. Length must equal the highest variable index |
| variables.buttons | object[] | { index, params?, payload? }[]. params items (string or { field } reference) fill URL button variables. payload sets a custom QUICK_REPLY postback value (≤256 chars) returned on the inbound click — use it to tell apart identical button labels across templates |
Per-contact personalization
By default a value in variables is a static string sent to everyrecipient. To send a different value per contact (e.g. each lead's own name in {{1}}), use either approach — both are fully backward compatible with the static string form:
- Field reference — make the value an object
{ field, default? }. Thefieldresolves against each contact:name(alias for the contact name),email,city,state,country, or any custom field viacustomFields.<key>.defaultis used when the field is empty for a contact. - Per-contact values — give each
audience.contacts[]entry its ownvariablesblock (raw-contacts mode only). The top-levelvariablesstill declares the variable structure and supplies fallback defaults.
Fixing “same value sent to every lead”
{{1}}for the recipient's name, send "body": [{ "field": "name" }] and pass each contact's name in the audience — every recipient then gets their own name.Quick-reply button payloads
Quick-reply buttons can carry a custom postback payload that comes back on the inbound click (in the campaign.button_click automation event and to your AI). This lets two templates that both use the label Interested be told apart programmatically. Set it via variables.buttons: [{ "index": 0, "payload": "interested_jan_promo" }] (≤256 chars). Omit it to keep the default behaviour (Meta returns the button label as the payload).
Examples
Immediate send to a segment
curl -X POST https://api.sendiee.com/v2.0/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brandNumber": "917012345678",
"campaignName": "Diwali sale 2026",
"templateId": "664a0bcd...",
"audience": {
"segmentIds": ["66a1..."],
"excludeTags": ["churned"]
},
"variables": {
"header": { "mediaUrl": "https://cdn.acme.com/diwali.jpg" },
"body": ["Surya", "DIWALI25"]
}
}'Scheduled send to a raw contact list
curl -X POST https://api.sendiee.com/v2.0/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brandNumber": "917012345678",
"campaignName": "Friday VIP drop",
"templateName": "vip_drop",
"language": "en_US",
"audience": {
"contacts": [
{ "phone": "919876543210", "name": "Surya" },
{ "phone": "918765432109", "name": "Ravi" }
]
},
"variables": {
"body": ["Friend"],
"buttons": [{ "index": 0, "params": ["VIP-FRI"] }]
},
"schedule": {
"sendAt": "2026-05-01T09:30:00",
"timezone": "Asia/Kolkata"
}
}'Personalized per contact (field reference)
curl -X POST https://api.sendiee.com/v2.0/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brandNumber": "917012345678",
"campaignName": "Nursing outreach",
"templateName": "dnp_nursing_naukri_4",
"language": "en",
"audience": {
"contacts": [
{ "phone": "919812345678", "name": "Asha" },
{ "phone": "919823456789", "name": "Karan" }
]
},
"variables": {
"body": [{ "field": "name", "default": "there" }]
}
}'Dry run (validate + preview, no send)
curl -X POST https://api.sendiee.com/v2.0/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brandNumber": "917012345678",
"templateName": "dnp_nursing_naukri_4",
"language": "en",
"dryRun": true,
"audience": {
"contacts": [
{ "phone": "919812345678", "name": "Asha" },
{ "phone": "919823456789", "name": "Karan" }
]
},
"variables": { "body": [{ "field": "name" }] }
}'{
"success": true,
"dryRun": true,
"data": {
"totalContacts": 2,
"mode": "raw",
"scheduleType": "immediate",
"perContact": false,
"resolvedSample": [
{
"phone": "919812345678",
"name": "Asha",
"body": [
"Asha"
],
"buttons": []
},
{
"phone": "919823456789",
"name": "Karan",
"body": [
"Karan"
],
"buttons": []
}
]
}
}Response
{
"success": true,
"data": {
"campaignId": "664a...",
"status": "processing",
"totalContacts": 432,
"scheduleType": "immediate",
"scheduledAt": null,
"timezone": "UTC"
}
}Variable validation failure (422)
{
"error": true,
"code": "CAMPAIGN_VARIABLES_INVALID",
"message": "variables payload does not match the template requirements",
"details": [
{
"path": "variables.header.mediaUrl",
"code": "HEADER_MEDIA_REQUIRED",
"message": "Template header is IMAGE — variables.header.mediaUrl is required"
},
{
"path": "variables.body",
"code": "BODY_PARAM_COUNT_MISMATCH",
"message": "Template body has 2 variables but variables.body has 1 entries"
}
]
}Empty audience (400)
{
"error": true,
"code": "CAMPAIGN_EMPTY_AUDIENCE",
"message": "Audience resolved to 0 contacts. Adjust your filters or contacts list."
}Send pacing