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 readAPI 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 RequirementRecommended ModelAPI Parameters Used
I maintain customers in my own CRM / ERP databaseDirect JSON Recipients (No contact sync needed)recipients: [{ phone, variables }]
I want PingStack to manage my contact database & segmentsSaved Contacts & Groupsgroup_ids: ["..."] or contact_ids: ["..."]
I want the exact same variable values for everyoneShared Template Variablestemplate_variables: { "1": "Sep", "2": "₹2500" }
Every recipient has different customized variable dataPer-Recipient Variable Mappingrecipients: [{ phone, variables: { "1": "...", "2": "..." } }]
The template has no variables (static message)No Variables RequiredPass group_ids, contact_ids, or recipients without variables
I have an Excel / CSV spreadsheet fileWeb UI File Upload or Backend JSON ScriptUpload 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

2. Audience + Template with Shared Variables (Same Values for All)

When sending to a group or contact list where all recipients should receive the **exact same placeholder values** (e.g. Month = "September", Due Date = "15th September"): Pass the `group_ids` or `contact_ids` along with a top-level `template_variables` object. Every resolved recipient in the audience receives these shared values. * **Dynamic Macros**: In `template_variables`, you can optionally use `{{name}}` (resolves to the contact's saved name) or `{{phone}}` (resolves to the contact's phone number).
Shared Variables Payload
{
  "group_ids": ["g_parents_grade_10"],
  "template_variables": {
    "1": "{{name}}",
    "2": "September 2026",
    "3": "15th Sep"
  }
}

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 CodeHTTP StatusCause & Resolution
VALIDATION_ERROR400No audience provided. Ensure at least one of "contact_ids", "group_ids", or "recipients" is non-empty.
NOT_FOUND404Campaign ID does not exist or belongs to another workspace.
LIMIT_EXCEEDED403Daily template message allowance reached for your plan. Upgrade required.
FEATURE_GATED403Campaign scheduling ("scheduled_at") is restricted to Growth and Pro plans.
CONFLICT409Idempotency-Key was already used with a differing request payload body.
INTERNAL_SERVER_ERROR500Unexpected backend error. Retry with backoff.