Appearance
Campaigns
Send a pre-approved template message to a large list of recipients in a single API call. Campaigns support both immediate and scheduled delivery.
Template Required
WhatsApp requires a pre-approved template for bulk campaigns. Text messages can only be sent within a 24-hour window after the recipient last contacted you — which cannot be guaranteed for bulk lists. Always use "message_type": "template" for campaigns.
Daily Tier Limit
Meta enforces a daily messaging limit per phone number based on its tier:
| Tier | Daily limit |
|---|---|
| Tier 1 (new numbers) | 1,000 conversations/day |
| Tier 2 | 10,000 conversations/day |
| Tier 3 | 100,000 conversations/day |
| Unlimited | No limit |
If your recipient count exceeds the number's remaining daily capacity, the API returns MESSAGING_TIER_LIMIT_EXCEEDED (HTTP 429) with a details object showing daily_limit, used_today, remaining, and requested. Split your campaign across multiple days or upgrade your tier by increasing message volume gradually.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/v1/campaigns | Create a bulk campaign |
GET | /api/v1/campaigns/{id} | Get campaign status and statistics |
GET | /api/v1/campaigns/{id}/recipients | List recipients with delivery status |
POST | /api/v1/campaigns/{id}/cancel | Cancel a scheduled campaign |
Create Campaign
POST /api/v1/campaigns
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_API_KEY |
Content-Type | Yes | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
whatsapp_account_id | string | Yes | Sender account ID. Find it in Dashboard → WhatsApp Numbers → copy icon next to API ID: |
message_type | string | Yes | Must be template |
template_name | string | Yes | Template name (e.g., order_confirmation) — same name used in sendTemplate |
template_language | string | No | Template language code (e.g., ar, en_US). Defaults to the first matching template. |
template_params | array | No | Array of string parameters for template placeholders |
recipients | array | Yes | List of recipients (max 50,000) |
recipients[].phone | string | Yes | Recipient phone number with country code |
recipients[].name | string | No | Recipient display name |
recipients[].variables | object | No | Per-recipient template variables |
campaign_name | string | No | Human-readable campaign name (max 120 characters) |
scheduled_at | string | No | ISO 8601 datetime for scheduled delivery (e.g., 2026-05-01T09:00:00). Must be in the future. |
timezone | string | No | IANA timezone for scheduled_at (e.g., Asia/Riyadh). Defaults to UTC. |
Example Request
bash
curl -X POST https://cubeconnect.io/api/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"whatsapp_account_id": "01JX...",
"message_type": "template",
"template_name": "order_confirmation",
"template_language": "ar",
"recipients": [
{ "phone": "+966501234567", "name": "Ahmed", "variables": { "1": "Ahmed", "2": "ORD-1234", "3": "CUBE20" } },
{ "phone": "+966509876543", "name": "Sara", "variables": { "1": "Sara", "2": "ORD-5678", "3": "CUBE15" } }
],
"campaign_name": "Offer Reminder",
"scheduled_at": "2026-05-01T09:00:00",
"timezone": "Asia/Riyadh"
}'php
$response = Http::withToken('YOUR_API_KEY')
->post('https://cubeconnect.io/api/v1/campaigns', [
'whatsapp_account_id' => '01JX...',
'message_type' => 'template',
'template_name' => 'order_confirmation',
'template_language' => 'ar',
'recipients' => [
['phone' => '+966501234567', 'name' => 'Ahmed', 'variables' => ['1' => 'Ahmed', '2' => 'ORD-1234', '3' => 'CUBE20']],
['phone' => '+966509876543', 'name' => 'Sara', 'variables' => ['1' => 'Sara', '2' => 'ORD-5678', '3' => 'CUBE15']],
],
'campaign_name' => 'Offer Reminder',
'scheduled_at' => '2026-05-01T09:00:00',
'timezone' => 'Asia/Riyadh',
]);javascript
const response = await fetch('https://cubeconnect.io/api/v1/campaigns', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
whatsapp_account_id: '01JX...',
message_type: 'template',
template_name: 'order_confirmation',
template_language: 'ar',
recipients: [
{ phone: '+966501234567', name: 'Ahmed', variables: { '1': 'Ahmed', '2': 'ORD-1234', '3': 'CUBE20' } },
{ phone: '+966509876543', name: 'Sara', variables: { '1': 'Sara', '2': 'ORD-5678', '3': 'CUBE15' } },
],
campaign_name: 'Offer Reminder',
scheduled_at: '2026-05-01T09:00:00',
timezone: 'Asia/Riyadh',
}),
})Response 201 Created
json
{
"success": true,
"data": {
"campaign_id": "01JX...",
"name": "Offer Reminder",
"status": "pending",
"message_type": "template",
"total_count": 2,
"sent_count": 0,
"failed_count": 0,
"scheduled_at": "2026-05-01T06:00:00Z",
"created_at": "2026-04-19T10:00:00Z"
}
}| Field | Type | Description |
|---|---|---|
data.campaign_id | string | Unique campaign ULID |
data.name | string|null | Campaign name |
data.status | string | Initial status: pending |
data.total_count | integer | Number of recipients |
data.sent_count | integer | Successfully sent (0 at creation) |
data.failed_count | integer | Failed deliveries (0 at creation) |
data.scheduled_at | string|null | UTC scheduled datetime, or null for immediate |
data.created_at | string | UTC creation datetime |
Get Campaign
GET /api/v1/campaigns/{id}
Path Parameters
| Parameter | Description |
|---|---|
id | Campaign ULID returned from create |
Example Request
bash
curl https://cubeconnect.io/api/v1/campaigns/01JX... \
-H "Authorization: Bearer YOUR_API_KEY"php
$response = Http::withToken('YOUR_API_KEY')
->get("https://cubeconnect.io/api/v1/campaigns/{$campaignId}");javascript
const response = await fetch(`https://cubeconnect.io/api/v1/campaigns/${campaignId}`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
})Response 200 OK
json
{
"success": true,
"data": {
"campaign_id": "01JX...",
"name": "Offer Reminder",
"status": "processing",
"message_type": "template",
"total_count": 500,
"sent_count": 320,
"failed_count": 12,
"scheduled_at": null,
"created_at": "2026-04-19T10:00:00Z"
}
}Campaign Status Values
| Status | Description |
|---|---|
pending | Created, waiting to start (or waiting for scheduled time) |
processing | Currently sending to recipients |
completed | All recipients processed |
cancelled | Cancelled before execution started |
failed | Campaign encountered a fatal error |
Get Campaign Recipients
GET /api/v1/campaigns/{id}/recipients
Returns a paginated list of recipients with their individual delivery status. Use this to audit exactly which numbers were reached and which failed.
Path Parameters
| Parameter | Description |
|---|---|
id | Campaign ULID returned from create |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 50 | Results per page (max 100) |
status | string | — | Filter by status: pending, sent, or failed |
Example Request
bash
curl "https://cubeconnect.io/api/v1/campaigns/01JX.../recipients?per_page=100&status=failed" \
-H "Authorization: Bearer YOUR_API_KEY"php
$page = $cube->getCampaignRecipients(
campaignId: '01JX...',
page: 1,
perPage: 100,
status: 'failed',
);
foreach ($page->recipients as $r) {
echo "{$r->phone}: {$r->status} — {$r->errorMessage}\n";
}
if ($page->hasMorePages()) {
$nextPage = $cube->getCampaignRecipients('01JX...', $page->currentPage + 1, 100, 'failed');
}typescript
const page = await cube.getCampaignRecipients('01JX...', {
page: 1,
perPage: 100,
status: 'failed',
})
for (const r of page.recipients) {
console.log(`${r.phone}: ${r.status} — ${r.errorMessage}`)
}
if (page.pagination.currentPage < page.pagination.lastPage) {
const next = await cube.getCampaignRecipients('01JX...', { page: 2, perPage: 100, status: 'failed' })
}Response 200 OK
json
{
"success": true,
"data": {
"campaign_id": "01JX...",
"recipients": [
{
"phone": "966501234567",
"name": "Ahmed",
"status": "sent",
"message_log_id": "01JY...",
"error_message": null,
"sent_at": "2026-04-19T10:05:32Z"
},
{
"phone": "966509876543",
"name": "Sara",
"status": "failed",
"message_log_id": null,
"error_message": "Invalid phone number",
"sent_at": null
}
],
"pagination": {
"current_page": 1,
"per_page": 50,
"total": 1000,
"last_page": 20
}
}
}Recipient Status Values
| Status | Description |
|---|---|
pending | Not yet processed |
sent | Message dispatched successfully |
failed | Delivery failed — see error_message for reason |
Cancel Campaign
POST /api/v1/campaigns/{id}/cancel
Cancels a scheduled campaign. Only campaigns in pending status can be cancelled.
Example Request
bash
curl -X POST https://cubeconnect.io/api/v1/campaigns/01JX.../cancel \
-H "Authorization: Bearer YOUR_API_KEY"php
$response = Http::withToken('YOUR_API_KEY')
->post("https://cubeconnect.io/api/v1/campaigns/{$campaignId}/cancel");javascript
const response = await fetch(`https://cubeconnect.io/api/v1/campaigns/${campaignId}/cancel`, {
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
})Response 200 OK
json
{
"success": true,
"data": {
"success": true
}
}Finding Your Account ID
The whatsapp_account_id is the ULID identifier for your WhatsApp Business account on CubeConnect.
To find it: Go to Dashboard → WhatsApp Numbers and click the copy icon next to API ID: on any connected number.
Webhooks
Track campaign progress in real time using webhook events:
| Event | Trigger |
|---|---|
campaign.created | Campaign accepted |
campaign.started | Execution begins |
campaign.completed | All messages processed |