Send a bulk SMS campaign. One call creates the campaign, expands your recipient list, debits the wallet and hands the batch to the network.
Endpoint
/v1/messaging/smssender is 3–11 letters or digits. If you have not registered your own sender ID, send with WRB — the shared Web Rabbit sender name, available to every workspace from day one. For branded messages register your own under Messaging → Bulk SMS → Sender IDs; once it is approved it becomes your default. Unregistered names are rejected by the networks with “Sender Id Not Allowed”.Request
| Field | Type | Description |
|---|---|---|
business_id | string | Optional on API-key calls — taken from the key. Required for dashboard sessions. A value that disagrees with the key is rejected. |
mode | enum | test or live. Optional on API-key calls — taken from the key. |
senderrequired | string | Approved sender ID, 3–11 alphanumeric characters. Defaults to the shared WRB name. |
messagerequired | string | Message body. Max 1600 characters. |
recipients | string[] | Numbers in local (0240000000) or international (233240000000) format. |
group_ids | string[] | Contact group ids. Merged with recipients; opted-out contacts are skipped. |
name | string | Campaign name shown in the dashboard. |
schedule_at | string | ISO timestamp. Omit to send immediately. The campaign is handed to the network scheduler when it accepts it, otherwise Web Rabbit holds it and releases it at the requested time. The response says which, in scheduled_by. |
timezone_id | string | Network time-zone id used with schedule_at. Ids come from GET /v1/messaging/reference?kind=timezone. |
validity_seconds | integer | How long the network keeps retrying an unreachable handset before giving up. 60 to 259200 seconds. |
dlt_entity_id | string | Regulator entity id, for markets that require registration (for example India DLT). |
dlt_template_id | string | Regulator template id. |
tlv | object[] | Regulator TLV tags, each { tag, tagValue }, passed straight through to the network. |
Recipients are de-duplicated and validated. A campaign accepts at most 10,000 numbers.
Idempotency-Key header and a retry of the same body returns the original campaign instead of sending twice. Reusing a key with a different body returns 409 idempotency_conflict. Same rules as Payments.curl -X POST https://api.webrabbitmedia.com/v1/messaging/sms \
-H "Authorization: Bearer wr_live_..." \
-H "Content-Type: application/json" \
-d '{
"business_id": "b0a1…",
"mode": "live",
"name": "October promo",
"sender": "WRB",
"message": "Our October sale is live — 20% off everything.",
"recipients": ["0240000000", "233201112233"]
}'Response
{
"ok": true,
"campaign_id": "3f0c…",
"provider_campaign_id": "664a…",
"cost": 4.40,
"recipients": 12,
"breakdown": [
{ "country": "Ghana", "iso2": "GH", "recipients": 10, "rate_per_segment": 0.20, "cost": 2.00 },
{ "country": "Nigeria", "iso2": "NG", "recipients": 2, "rate_per_segment": 0.20, "cost": 0.40 }
],
"credit_left": 4180
}| Field | Type | Description |
|---|---|---|
campaign_id | string | Web Rabbit campaign id. Use it to pull delivery reports. |
provider_campaign_id | string | Upstream batch id, null while queued. |
cost | number | Credits debited: the sum of segments × rate for each recipient, priced by destination country. |
breakdown | array | Cost grouped by destination country: recipients, rate per segment and subtotal. |
recipients | number | Valid, de-duplicated numbers accepted. |
rejected | number | Recipients the network refused. Their credits are refunded at exactly what they cost. |
simulated | boolean | Present and true for test-mode sends. |
What a message costs
Messages are paid from your messaging wallet. SMS from a bundle package is used before wallet balance, and Web Rabbit can also add credit to your wallet directly.
Price is per segment and depends on the country the recipient's number belongs to, because that is how the networks charge us. Every destination is priced on that country's local network rate: we take the carrier price in euro, convert it to cedis and apply our margin:
price per segment = ceil(carrier EUR rate × EUR→GHS × 3) , minimum GHS 0.20
campaign cost = Σ segments × price per segment (per recipient)| Field | Type | Description |
|---|---|---|
Ghana (+233) | GHS 0.20 | Per 160-character segment. The floor price. |
Nigeria (+234) | GHS 0.20 | Local network rate. |
Kenya (+254) | GHS 1.35 | Local network rate. |
South Africa (+27) | GHS 0.36 | Local network rate. |
Senegal (+221) | GHS 0.76 | Local network rate. |
Anywhere else | GHS 3.96 | Fallback for destinations outside the published list. |
The full list of 52 African destinations, and the exact price we will charge you, is shown live under Messaging → Wallet → Price per country. Prices move with the carrier sheet and the exchange rate; the breakdown in every send response tells you precisely what you were charged and why. Wallets stay in GHS whatever the destination.
Numbers may be written as Ghana local (0240000000) or in full international form (233240000000, 2348030000000). A local number is always read as Ghana.
Multiple sender IDs
A workspace can hold as many approved sender IDs as it needs — one per product line, for example RIDES, DELIVERY and SAFETY. There is no limit and no extra cost; each name is registered and approved separately with the networks, which typically takes 1–3 business days. Register them under Messaging → Sender IDs.
Sender selection is per request: set sender to any approved name on your workspace for that mode. Sending an unapproved name is rejected with HTTP 400 before the message is charged, and WRB is always available as a fallback while a new name is in review.
When you request a name we ask for three things: the message type — transactional, promotional, or both — the country the name is registered for (Ghana by default), and a short use case with a sample message. The networks only accept one of those three message types, so it is a choice rather than free text; your use case and sample travel with the request as the supporting detail. If a request is saved but the network has not picked it up yet, use Resend to network on the row instead of creating the name again.
Delivery reports
message.delivered, message.failed and message.rejected and we push one signed receipt per recipient, with the same HMAC-SHA256 scheme as payment events. Every submitted message gets exactly one terminal event; if the network never reports, we close it after 24 hours as undelivered. The endpoint below is a fallback for forcing a refresh./messaging-statusPoll for per-recipient outcomes. Each call reconciles the campaign’s messages with the network’s report and returns how many rows changed.
curl -X POST https://api.webrabbitmedia.com/v1/messaging/messaging-status \
-H "Authorization: Bearer wr_live_..." \
-H "Content-Type: application/json" \
-d '{ "campaign_id": "3f0c…" }'{ "ok": true, "updated": 12 }Message statuses move queued → submitted → delivered, or to undelivered, failed or rejected. Unknown network codes stay submitted until the next report, and at most 24 hours — after that we settle the message as undelivered with provider_status: "NO_DLR" and emit message.failed.
