Send a WhatsApp campaign through the API
Send a WhatsApp campaign through the API: marketing template, recipients, launch, plan quotas, Meta's marketing limits and delivery tracking.
To send a WhatsApp campaign through the API with Genuka WA, you get a MARKETING template
approved by Meta, create the campaign with POST /api/v1/campaigns and its recipient list, then
launch it with POST /api/v1/campaigns/{id}/launch. Every recipient is tracked through to read,
and Meta bills delivered messages directly to your own WhatsApp Business account.
Last updated October 8, 2026
What you need before sending a campaign
- A connected number and its
connectionId— see Connecting a number. - An API key, used server-side only — see Authentication.
- A payment method on your Meta account. Genuka is a Meta Tech Provider, not a BSP: Meta bills
your WhatsApp Business Account (WABA) directly, and a client onboarded by a Tech Provider must
add its own payment method
(Meta, Partners).
Without one, the template gets approved but every send comes back with error
131042. - Recipients who agreed to hear from you. That is a WhatsApp rule, not a best practice — the opt-in section further down covers it.
How do I create the marketing template?
Outside the 24-hour customer service window, only an approved template is delivered. A campaign
always uses one. Submit it with the MARKETING category:
curl -X POST https://wa.genuka.com/api/v1/templates \
-H "Authorization: Bearer $GENUKA_WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connectionId": "con_1",
"name": "october_sale",
"language": "en_US",
"category": "MARKETING",
"components": [
{ "type": "BODY",
"text": "Hi {{1}}, 20% off the whole shop until Sunday.",
"example": { "body_text": [["Awa"]] } },
{ "type": "BUTTONS", "buttons": [
{ "type": "URL", "text": "See the offers", "url": "https://example.com/sale" }
] }
]
}'The response is a 201 carrying the template's id and its status. Meta reviews it —
review can take up to 24 hours
(Meta, Template fundamentals).
The verdict reaches your webhooks (template.status_changed) and
GET /api/v1/templates/{id}. A campaign only goes out with an approved template.
Every body variable ({{1}}, {{2}}…) needs an example, or the template is rejected. The
API reference covers media headers, named parameters and buttons.
How do I create and launch the campaign?
Create the campaign
POST /api/v1/campaigns takes four fields, all required: connectionId, templateId, name
and recipients. Each recipient carries its number in international format (+ and country
code) and its own variables. The campaign is created as draft: nothing is sent yet.
Launch it
POST /api/v1/campaigns/{id}/launch sends every recipient still pending and answers with the
count: sent, failed, skipped. sent means Meta accepted the message, not that it arrived —
delivery and read come afterwards, by webhook.
Track the statuses
Read the counters on GET /api/v1/campaigns/{id} and the per-recipient detail on
GET /api/v1/campaigns/{id}/recipients. The tracking section further down covers both.
# 1. Create the campaign (draft)
curl -X POST https://wa.genuka.com/api/v1/campaigns \
-H "Authorization: Bearer $GENUKA_WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connectionId": "con_1",
"templateId": "tpl_123",
"name": "October sale",
"recipients": [
{ "to": "+237690000001", "variables": ["Awa"] },
{ "to": "+237690000002", "variables": ["Paul"] }
]
}'
# { "data": { "id": "cmp_9", "name": "October sale", "status": "draft", "_count": { "recipients": 2 } } }
# 2. Launch it
curl -X POST https://wa.genuka.com/api/v1/campaigns/cmp_9/launch \
-H "Authorization: Bearer $GENUKA_WA_API_KEY"
# { "data": { "sent": 2, "failed": 0, "skipped": 0 } }variables is a positional array for the body parameters. For a media header, a dynamic button
or named parameters, pass an object instead —
{ "body": [...], "bodyNamed": { "first_name": "Awa" }, "header": {...}, "buttons": [...] } —
the same shape as sending a template. A named-parameter template's values
go in bodyNamed: put in body, they would be sent as positional parameters.
Why is the launch refused?
These checks run before the first send: a refused launch has sent nothing. The most common refusals:
| Status | Code | Cause |
|---|---|---|
402 | plan_limit_messages | The list does not fit in what is left of the period's quota. Every pending recipient counts, including those later excluded as opted out |
402 | subscription_past_due | The trial or the paid period has lapsed |
400 | send_template | The template is not approved (in review, rejected, paused) — the message says which |
409 | send_config | The number was disconnected from the API in the WhatsApp Business app (coexistence): reconnect it |
409 | number_released | The number was released from your plan: reconnect it through Embedded Signup |
403 | account_deactivated | The client that owns the number is suspended |
404 | campaign_not_found | The id does not exist or is outside your key's scope |
On creation, a templateId and a connectionId belonging to two different clients answer
400 template_connection_mismatch, and a list without a single usable to answers
400 no_valid_recipients. Other codes are in the error reference.
How many messages can a campaign send?
Genuka never charges for a message. Your plan does include a quota of outbound messages per paid number per billing period, shared between API sends and campaign recipients. The period's quota is: numbers paid for × the tier's per-number allowance. The counter starts again from zero with each new period: every month on a monthly subscription, at renewal on an annual one.
| Tier | Max numbers | Messages included per number per period |
|---|---|---|
| Starter | 5 | 500 |
| Growth | 15 | 5,000 |
| Scale | 100 | 30,000 |
| Enterprise | custom | custom |
Example: 3 numbers on Growth give 15,000 messages for the period. The 7-day free trial runs on the
Growth tier with one number, so 5,000 messages. Only messages Meta accepted are counted: a send
Meta refuses on the call consumes nothing, but a message accepted and then reported as failed
by webhook stays counted. Prices are on the pricing page and in Markdown at
/pricing.md.
What limits does Meta put on marketing campaigns?
The Genuka quota is not the only ceiling. Meta enforces four of its own, whatever your plan:
| Meta limit | What it says | What you see |
|---|---|---|
| Messaging limit | Unique recipients you can reach outside a customer service window, per moving 24 hours, at business portfolio level: 250 for a new portfolio, then 2,000, 10,000, 100,000, unlimited (docs) | WhatsApp Manager, Account tools > Messaging limits (same page). The messagingLimitTier from GET /api/v1/numbers?refresh=true is only indicative: it comes from a field Meta has deprecated and can be empty |
| Per-user marketing limit | WhatsApp may not deliver a marketing template to someone who receives many and reads few. Wait at least 24 hours before resending (docs) | Error 131049 on the status webhook, recipient failed |
| US numbers | WhatsApp does not currently deliver marketing templates to US phone numbers (same page) | Recipient fails |
| Throughput | 80 messages per second per number by default, 20 for a coexistence number (throughput, coexistence) | The launch is paced to it |
The per-user marketing limit is not currently active for messages sent from or to the European Economic Area, the United Kingdom, Japan and South Korea (same Meta page).
A campaign's MARKETING templates are first offered to Meta's
Marketing Messages API;
if your account does not have access to it, the send falls back to the classic endpoint without
stopping the campaign.
How much does a campaign cost on Meta's side?
Meta charges per delivered message, at the rate for the template's category and the recipient's country calling code, on your business portfolio (Meta, Pricing). Genuka adds no markup and never handles that payment.
Do I need opt-in for a WhatsApp campaign?
Yes. The WhatsApp Business policy only lets you contact someone who gave you their number and confirmed they want to receive your messages (WhatsApp Business Messaging Policy). Meta adds that the request must name your business and state clearly what the person is opting in to, through any channel you like — website, SMS, paper form (Meta, Get opt-in). Quality that stays low for a sustained period gets the number rate-limited by Meta (same page).
Genuka enforces marketing opt-outs on campaigns for you:
- When a user stops marketing messages from within WhatsApp, Meta announces it by webhook and
Genuka records it. A send refused with
131050("this recipient has chosen to stop receiving marketing messages") is recorded too. - When a
MARKETINGcampaign launches, those contacts are excluded before any send and markedskipped, with the reason. They consume neither quota nor Meta billing. UTILITYandAUTHENTICATIONcampaigns are not filtered: they are not marketing messages.
A keyword of your own ("reply STOP") is not interpreted automatically: replies reach you on
message.received, and removing those contacts from your lists is up to you.
How do I track a campaign's results?
GET /api/v1/campaigns/{id} returns the campaign, its template, its number and a stats object
counting recipients by status:
{
"data": {
"id": "cmp_9",
"name": "October sale",
"status": "completed",
"template": { "id": "tpl_123", "name": "october_sale", "language": "en_US", "status": "approved" },
"_count": { "recipients": 1200 },
"stats": { "read": 640, "delivered": 410, "sent": 95, "failed": 41, "skipped": 14 }
}
}Each recipient is counted once, under its most advanced status: a read message sits in read,
not in delivered. The delivery rate therefore reads (delivered + read) / (total − skipped).
| Status | Meaning |
|---|---|
pending | Not sent yet |
sent | Accepted by Meta |
delivered | Reached the phone |
read | Read — only if the recipient has read receipts on |
failed | Refused, with Meta's reason in errorMessage |
skipped | Excluded at launch: opted out of marketing |
For failures, GET /api/v1/campaigns/{id}/recipients?status=failed&limit=1000 lists each recipient
with errorMessage, sentAt, deliveredAt, readAt and failedAt (200 rows by default, 1,000
at most). In real time, subscribe an endpoint to message.delivered, message.read,
message.failed and user_preference.stopped — see Webhooks.
How do I resend to recipients blocked by the marketing limit?
The per-user marketing limit shows up after the send: Meta accepts the message, then the status
webhook comes back failed with error 131049
(Meta, Per-user limits).
The recipient moves to failed, with Meta's text in errorMessage (Meta describes this error as
"This message was not delivered to maintain healthy ecosystem engagement"). Calling launch again
will not resend to them: it only picks up pending recipients.
To try those people again, wait at least 24 hours, list
GET /api/v1/campaigns/{id}/recipients?status=failed, keep the ones carrying that reason (or the
131049 code received on message.failed) and create a new campaign with them. In the rarer case
where Meta refuses the send itself with 131049, the recipient stays pending, and calling
launch again 24 hours later picks it up.
Current limitations
- The launch is synchronous and time-bound. The call works through the list one recipient
after another: each send waits for Meta's answer, then for its status to be stored, so the real
rate stays well below the number's ceiling. Past about 2 minutes, the CDN in front of the API
answers
524— an error page, not JSON — while sending carries on server-side (Cloudflare, error 524). Keep campaigns to a few hundred recipients and split a large audience into several campaigns. - After a
524, follow the campaign instead of launching it again. PollGET /api/v1/campaigns/{id}untilstatusis no longerrunning. Never calllaunchon arunningcampaign: nothing prevents it today, and two runs in parallel would send the message twice to the recipients the first run has not reached yet. - A run that goes on too long is cut off. The server does not let a call run forever. If the
campaign is still
running15 minutes after the call, the run was cut short: the recipients stillpendingwere not sent, and callinglaunchagain picks them up. - No scheduling. There is no date field: call
launchwhen you want it to go, from your own scheduled job.
What if I want a UI rather than an API?
Genuka WA has no campaign console: /api/v1/campaigns is an integration primitive (template +
recipients + statuses). If you want to segment a customer base and launch campaigns without
writing code, Genuka Core, Genuka's business management platform, does it:
it categorizes your customers, analyzes their buying behavior and launches targeted WhatsApp, SMS
and email campaigns.
FAQ
Can I send a campaign to contacts who never messaged me?
Yes, with an approved template and their opt-in. That is exactly what a template is for: it is the only message delivered outside the 24-hour window. Meta's messaging limit (250 unique recipients per 24 hours for a new portfolio) applies to those sends.
How much does a WhatsApp campaign cost with Genuka WA?
Two separate lines. Meta bills each delivered template to your business portfolio, by category and recipient country. Genuka charges a subscription per WhatsApp number, with a message allowance included, and takes no markup on Meta's rates.
What happens if a recipient opted out?
On a MARKETING campaign, they are excluded before sending and marked skipped. They do not use
your quota and Meta charges you nothing for them.
Why is sent high but delivered low?
sent only means Meta accepted the message. Delivery receipts follow by webhook; if they do not
come, look at failed and at errors 131049 (marketing limit) or 131026 (recipient unreachable).
Can I launch the same campaign again?
Yes, once it is no longer running (completed or failed): launch then only sends recipients
still pending, never a message that already went out. Do not launch it again while it is
running, even after a 524: wait for it to leave that status, or 15 minutes if it stays there
(see the limitations above).
Sources
- Meta — Messaging limits
- Meta — Per-user marketing template message limits
- Meta — Throughput
- Meta — Pricing on the WhatsApp Business Platform
- Meta — Get opt-in for WhatsApp
- Meta — Template fundamentals
- Meta — Marketing Messages API: get started
- Meta — Partners (Tech Providers and Solution Partners)
- Meta — Error codes
- Meta — Onboard WhatsApp Business app users
- WhatsApp — Business Messaging Policy
- Cloudflare — Error 524: a timeout occurred
- Genuka — genuka.com
WhatsApp order notifications from your backend
Send WhatsApp order confirmations, shipping and delivery updates from your backend: utility templates, variables, idempotency and delivery statuses.
Receive WhatsApp replies and statuses via webhook
Receive WhatsApp replies and delivery statuses on your server: register a webhook, verify the HMAC signature, deduplicate, handle retries and replay.