DocsCore APIs
Campaigns & Broadcasts API
Orchestrate high-volume broadcast campaigns across saved contacts, audience groups, or direct external CRM recipient lists with template variables.
8 min read•API v1
Campaigns Overview
The PingStack Campaigns API allows developers and backend systems to dispatch bulk WhatsApp template notifications to hundreds or thousands of recipients in a single background operation.
When a campaign is launched:
1. The API validates plan daily template allowances and idempotency keys.
2. Recipients from selected groups, saved contacts, and direct CRM arrays are normalized and automatically deduplicated by phone number.
3. The batch job is enqueued into Redis BullMQ (`campaign-queue`), immediately returning an asynchronous `running` status without risking HTTP request timeouts.
4. The background worker resolves template positional placeholders, persists message records, and queues dispatches to Meta WhatsApp Cloud API with built-in rate smoothing.
When to Use Campaigns vs. Messages API
PingStack provides two distinct ways to send outbound WhatsApp messages:
* **Messages API (`POST /api/v1/messages`)**: Best for individual, real-time transactional alerts triggered by immediate user actions (e.g. OTP verification, login confirmation, instant order payment receipt).
* **Campaigns API (`POST /api/v1/campaigns/{id}/launch`)**: Best for batch notifications, fee reminders, monthly statements, marketing announcements, and broadcast newsletters sent to multiple recipients simultaneously with aggregated delivery reporting.
Audience Decision Guide
Choose the integration model that best fits your system architecture:
| Your Requirement | Recommended Model | API Parameters Used |
|---|---|---|
| I maintain customers in my own CRM / ERP database | Direct JSON Recipients (No contact sync needed) | recipients: [{ phone, variables }] |
| I want PingStack to manage my contact database & segments | Saved Contacts & Groups | group_ids: ["..."] or contact_ids: ["..."] |
| I want the exact same variable values for everyone | Shared Template Variables | template_variables: { "1": "Sep", "2": "₹2500" } |
| Every recipient has different customized variable data | Per-Recipient Variable Mapping | recipients: [{ phone, variables: { "1": "...", "2": "..." } }] |
| The template has no variables (static message) | No Variables Required | Pass group_ids, contact_ids, or recipients without variables |
| I have an Excel / CSV spreadsheet file | Web UI File Upload or Backend JSON Script | Upload in PingStack Console or parse in backend to JSON |
1. Audience + Template with No Variables
If your approved Meta WhatsApp template does not contain any variable placeholders (e.g. a general holiday announcement or terms update), you can launch the campaign by simply providing the target audience. No variable mapping is required.
* **Audience**: `group_ids`, `contact_ids`, or `recipients`
* **Variables**: None
3. Template with Per-Recipient Variables (Customized Values)
When each recipient requires unique, individual variable data (e.g. individual invoice amounts or student fee balances):
* **Rahul** (919876543210) → Var 1: "Rahul", Var 2: "₹2,500"
* **Amit** (919876543211) → Var 1: "Amit", Var 2: "₹1,800"
* **Neha** (919876543212) → Var 1: "Neha", Var 2: "₹3,200"
Pass the `recipients` array where each item contains the recipient's phone number and their specific positional `variables`.
*Important: The phone number is the recipient identity, NOT variable 1. Template variables remain positional placeholders corresponding to Meta template slots {{1}}, {{2}}, etc.*
Per-Recipient Payload
{
"recipients": [
{
"phone": "919876543210",
"name": "Rahul Sharma",
"variables": {
"1": "Rahul",
"2": "₹2,500",
"3": "15 Sep"
}
},
{
"phone": "919876543211",
"name": "Amit Kumar",
"variables": {
"1": "Amit",
"2": "₹1,800",
"3": "15 Sep"
}
}
]
}Complete Lifecycle Code Examples
### Step 1: Create Campaign Draft (`POST /api/v1/campaigns`)
curl -X POST https://app.pingstack.in/api/v1/campaigns \
-H "Authorization: Bearer ps_secret_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "September Fee Reminders",
"template_name": "fee_reminder"
}'Step 2: Launch Campaign with Audience (`POST /api/v1/campaigns/{id}/launch`)
Pass `group_ids`, `contact_ids`, or direct `recipients` with positional variables:
curl -X POST https://app.pingstack.in/api/v1/campaigns/CAMPAIGN_ID/launch \
-H "Authorization: Bearer ps_secret_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fee-batch-2026-09-01" \
-d '{
"recipients": [
{
"phone": "919876543210",
"variables": { "1": "Rahul", "2": "₹2,500", "3": "15 Sep" }
},
{
"phone": "919876543211",
"variables": { "1": "Amit", "2": "₹1,800", "3": "15 Sep" }
}
]
}'Status Lifecycle & Querying Results
* **Campaign Statuses**: `draft` → `scheduled` (Growth plan) → `running` (processing in BullMQ) → `completed` (or `failed`).
* **Query Live Metrics**: `GET /api/v1/campaigns/{id}/results` returns delivery counts and read rates:
Results Response Format
{
"success": true,
"data": {
"campaign_id": "c8a1b2c3-1d2e-4f5a-b6c7-8d9e0f1a2b3c",
"name": "September Fee Reminders",
"status": "completed",
"metrics": {
"total_messages": 250,
"pending": 0,
"sent": 248,
"delivered": 245,
"read": 192,
"failed": 2,
"delivered_rate_pct": 98,
"read_rate_pct": 77
}
},
"request_id": "req_9f0e1d2c"
}Idempotency, Quotas & Rate Limits
* **Idempotency**: Always pass an `Idempotency-Key` header when launching campaigns. If network latency triggers an automatic client retry within 24 hours, PingStack replays the existing launch response without creating duplicate message jobs.
* **Plan Quota Checking**: Before queueing campaign messages, PingStack checks your daily template message allowance. If your plan limit is insufficient, the launch request returns `HTTP 403 LIMIT_EXCEEDED`.
* **Rate Smoothing**: Background dispatches are rate-smoothed across Redis workers to prevent Meta Cloud API throughput spikes.
Common Errors & Troubleshooting
| Error Code | HTTP Status | Cause & Resolution |
|---|---|---|
| VALIDATION_ERROR | 400 | No audience provided. Ensure at least one of "contact_ids", "group_ids", or "recipients" is non-empty. |
| NOT_FOUND | 404 | Campaign ID does not exist or belongs to another workspace. |
| LIMIT_EXCEEDED | 403 | Daily template message allowance reached for your plan. Upgrade required. |
| FEATURE_GATED | 403 | Campaign scheduling ("scheduled_at") is restricted to Growth and Pro plans. |
| CONFLICT | 409 | Idempotency-Key was already used with a differing request payload body. |
| INTERNAL_SERVER_ERROR | 500 | Unexpected backend error. Retry with backoff. |