Send messages
Message customers on WhatsApp, Instagram and Messenger, manage WhatsApp templates, send campaigns and login codes.
Everything here uses the same token as the rest of the API (a workspace key mik_… or an app's mat_…). Find the channel ids with GET https://api.warid.chat/api/v1/channels.
The 24-hour rule
WhatsApp, Instagram and Messenger only accept normal messages (text and images) within 24 hours of the customer's last message. After that:
- WhatsApp: send an approved template. Templates are also the only way to message someone first.
- Instagram and Messenger: wait until the customer writes again.
If the window is closed, the API answers 422 with "error": "window_closed" and sends nothing. Each conversation's windowExpiresAt tells you when it closes.
Send a message
Reply in a conversation with text, an image (a public JPEG or PNG link, up to 5 MB), or both:
curl -X POST https://api.warid.chat/api/v1/conversations/CONVERSATION_ID/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"text": "Here is your receipt", "imageUrl": "https://yourapp.com/receipts/1042.png"}'Or skip looking up the conversation. Send to a contact on any channel, or to a phone number on a WhatsApp channel:
# A contact (their latest conversation, or the one on channelId)curl -X POST https://api.warid.chat/api/v1/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"contactId": "CONTACT_ID", "text": "Your table is ready"}' # A phone number, first message on WhatsApp: use a templatecurl -X POST https://api.warid.chat/api/v1/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "to": "+9647701234567", "template": {"name": "order_ready", "language": "en_US", "params": ["Sara", "#1042"]}}'The response is the stored message. status is sent, then moves to delivered and read (listen for message.updated webhooks); failed comes with an error. Messages from the API show in the inbox and don't pause the AI agent.
WhatsApp templates
A template is a message Meta approved in advance. Templates belong to the WhatsApp account behind a number, so numbers on one account share them. Businesses can also manage them in Channels → Templates.
List them (scope templates:read). params is how many values a send needs, one per entry in vars: the body's blanks, then the title's, then each link button's; only APPROVED ones can be sent.
curl https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates -H "Authorization: Bearer $META_INBOX_KEY"[ { "id": "1273849204", "name": "order_ready", "language": "en_US", "category": "UTILITY", "status": "APPROVED", "rejectedReason": null, "body": "Hi {{1}}, your order {{2}} is ready.", "params": 2, "vars": [{ "key": "1", "part": "body" }, { "key": "2", "part": "body" }], "header": null, "footer": null, "buttons": [] }]Create one (scope templates:write). Put {{1}}, {{2}}, … where values go and give an example for each:
curl -X POST https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{ "name": "order_ready", "language": "en_US", "category": "UTILITY", "body": "Hi {{1}}, your order {{2}} is ready.", "examples": ["Sara", "#1042"], "buttons": [{ "type": "url", "text": "Track order", "url": "https://yourapp.com/orders" }] }'name: lowercase letters, numbers and underscores.category:UTILITY(orders, bookings, reminders),MARKETING(offers, news) orAUTHENTICATION(login codes, see below).- Optional:
headerandfooter(short text lines), up to 10buttons(quick_reply,urlorphone).
It starts as PENDING. Meta usually reviews it within minutes; then it's APPROVED or REJECTED with a rejectedReason. Check with GET …/templates?name=order_ready, or subscribe a webhook to template.updated.
Delete one with DELETE https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates/order_ready. It's removed in every language, and Meta blocks the name for 30 days.
Campaigns
Send one approved WhatsApp template to many customers, now or later, and follow how far each message got. Scopes campaigns:write to send and campaigns:read for results. WhatsApp channels only: Instagram and Messenger don't allow marketing broadcasts. Businesses can also do all of this from Campaigns in the dashboard.
# Everyone tagged vip, with their name in {{1}}curl -X POST https://api.warid.chat/api/v1/campaigns \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "name": "Eid sale", "template": {"name": "eid_offer", "language": "en"}, "params": [{"field": "name", "fallback": "there"}, {"text": "EID20"}], "audience": {"type": "tags", "tags": ["vip"]}}'audience:{ "type": "tags", "tags": [...] }(conversations on that number with any of the tags),{ "type": "everyone" }(everyone who chatted on it), or{ "type": "list", "recipients": [{ "phone": "+9647701234567", "name": "Sara", "values": { "code": "SARA10" } }] }for your own list, up to 10,000 people.params: one per entry in the template'svars, in order.{ "text": "EID20" }is the same for everyone;{ "field": "name" }is the customer's name and{ "field": "code" }reads theirvalues.code. Add afallbackfor people without one, or they're skipped.headerMedia:{ "link": "https://…" }when the template has a picture, video or file on top (headerFormatin the template list).scheduledAt: an ISO time up to 30 days ahead. Leave it out to send now.
Customers who reply STOP (or *إيقاف*) are opted out and skipped from then on; START opts them back in. You can set it yourself with PATCH /contacts/{id} and { "optedOut": true }.
Messages go out at about 20 a second and land in each customer's conversation, so replies show up in the inbox. Follow along with GET https://api.warid.chat/api/v1/campaigns/CAMPAIGN_ID:
{ "id": "…", "status": "sent", "stats": { "total": 1200, "queued": 0, "sent": 1180, "delivered": 1150, "read": 820, "replied": 96, "failed": 12, "skipped": 8 }}status is scheduled, sending, paused (out of credit: error is no_credit, and it carries on by itself after a top-up), sent, canceled, or failed when WhatsApp stopped it early (error has its code, e.g. 132015 for a paused template). GET …/campaigns/CAMPAIGN_ID/recipients?status=failed lists people with a reason: opted_out, invalid_phone, missing_value, canceled, or WhatsApp's code (131049 means WhatsApp held back a marketing message to that person). POST …/campaigns/CAMPAIGN_ID/cancel stops one that hasn't finished. Subscribe a webhook to campaign.updated to hear when it starts and finishes.
Verification codes (OTP)
Send a one-time code on WhatsApp and check what the user types. We make the code, send it, keep only a hash, and expire it. Scope otp:send, WhatsApp channels only.
1. Create the template once. Meta writes the text ("123456 is your verification code.") and adds a Copy code button:
curl -X POST https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"name": "login_code", "language": "en", "category": "AUTHENTICATION", "codeExpiresMinutes": 10}'Wait until it's APPROVED. (Or click Verification codes in Channels → Templates.)
2. Send a code when the user asks to sign in:
curl -X POST https://api.warid.chat/api/v1/otp \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "to": "+9647701234567"}'{ "id": "6f1c2a7e-0b7d-4e57-9a51-2f0c9e3b8d11", "to": "+9647701234567", "channelId": "CHANNEL_ID", "expiresAt": "2026-10-16T12:10:00.000Z", "template": { "name": "login_code", "language": "en" }}Options: length (4–8 digits, default 6), expiresInMinutes (1–30, default 10), and template ({ "name": "login_code", "language": "ar" }) to pick one when you have several. The first approved authentication template is used otherwise.
3. Check the code the user typed:
curl -X POST https://api.warid.chat/api/v1/otp/verify \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"to": "+9647701234567", "code": "482913"}'{ "valid": true, "id": "6f1c2a7e-0b7d-4e57-9a51-2f0c9e3b8d11", "to": "+9647701234567" }A wrong or old code returns 200 with "valid": false and a reason: wrong_code (with attemptsLeft), expired, already_used, too_many_attempts or not_found. Sign the user in only when valid is true.
Rules that keep it safe:
- Each code works once. Sending a new code to a number ends the previous one.
- 5 wrong tries end a code; send a new one.
- Per number: one code every 30 seconds and 5 an hour. Over that,
429withotp_too_soonorotp_rate_limitedand aRetry-Afterheader (seconds). - Codes aren't shown in the inbox.
Errors
| Status | error | What to do |
|---|---|---|
| 422 | window_closed | More than 24 hours since the customer wrote. On WhatsApp send a template. |
| 422 | template_not_found | No approved template with that name and language. |
| 422 | template_params_mismatch | Send exactly params values, none empty. |
| 422 | templates_need_whatsapp | Templates and codes only work on official WhatsApp channels. |
| 422 | template_rejected | Meta refused the new template; message says why (often a duplicate name). |
| 422 | examples_must_match_params | Give one example per {{n}} in the body. |
| 422 | otp_template_missing | Create an approved AUTHENTICATION template first. |
| 422 | invalid_phone | Use international format, e.g. +9647701234567. |
| 422 | no_conversation | The contact has no conversation (on that channel). |
| 422 | no_recipients | Nobody in the audience can get it (all opted out, invalid or missing a value). |
| 422 | header_media_required | The template has a picture, video or file on top; send headerMedia.link. |
| 409 | campaign_finished | The campaign already finished, so it can't be canceled. |
| 429 | otp_too_soon, otp_rate_limited | Wait Retry-After seconds. |
| 502 | send_failed, whatsapp_unavailable | WhatsApp didn't accept the request; message has its reason when there is one. |

