SMS + WhatsApp API · Africa
Send SMS and WhatsApp across Africa from one API.
POST a message with your organization's key. O3Notify picks the carrier for the destination: a local operator where we have one (Orange in Guinea today), Twilio everywhere else, Meta's Cloud API for WhatsApp. Then it tells you the moment the message lands. One key, one bill.
Private while we onboard the first teams. No SDK to install. Any HTTP client, a bearer key, one JSON body.
Message lifecycle
Four states. Every change is an event.
The 202 is an acceptance, not a delivery. Poll GET /v1/messages/:id, or configure a webhook and stop polling.
- queued
- sent
- deliveredfailed
- queued
- Accepted. The row insert is the enqueue; there is no second queue to drift out of sync with.
- sent
- A worker claimed it, picked the carrier, submitted it and stored the carrier's own id.
- delivered
- The carrier's delivery receipt arrived, by callback or by poll.
- failed
- The carrier refused it, or the receipt said so. error_code carries the carrier's reason.
Provider routing
Routing is a table, not a black box.
Every SMS gets its country from the recipient's E.164 number, then the worker reads routing_rules: rows the operator edits, never code it redeploys. Three things decide the carrier, in this order:
- 1
Destination country
Rules for the number's own country come first. The
*catch-all is a rule too, so every country is routable even with no rule of its own. - 2
Availability
A carrier whose circuit breaker is open is skipped for the next candidate. A blip at one carrier is a reroute, not a failed message.
- 3
Price
Within the same priority, the cheaper route wins.
WhatsApp never touches the table. channel: "whatsapp" goes straight to Meta's Cloud API, which reaches every country the same way at the same price.
Channels
SMS where it's local. WhatsApp without the Meta paperwork.
channel: "sms"
Up to 1,600 characters, concatenated. Where we have a local carrier, messages go out through it: Guinean numbers through Orange's SMS gateway today. Everything else goes through Twilio. An optional sender, an alphanumeric id of up to 11 characters, is used wherever the destination honours it and falls back to the platform default where it doesn't.
- to
- E.164, any country with a rule
- body
- 1–1600 characters
- sender
- optional, ≤ 11 characters
channel: "whatsapp"
Business-initiated WhatsApp has to use a Meta-approved template. Yours is already filed: your body is delivered inside O3Notify's utility template with your organization's name on it, so you never submit a template or wait on a business review. Translations in English and French (language: "fr").
Message from Acme Bank:
Your order 8812 is ready for pickup.
Sent via O3Notify.
One-time codes take the dedicated authentication template instead: pass template: { name: "o3notify_otp", params: ["482913"] } and the recipient gets the code with a copy button.
Webhooks
Signed, retried, never silent.
Set one URL for your organization. Every status change is POSTed to it with four fields and a signature over the raw body. Verify it with your endpoint's secret and one HMAC:
expected = "sha256=" + hex(HMAC_SHA256(secret, raw_body))
Delivery is attempted up to five times, with retries after 1 minute, 5 minutes, 30 minutes and 2 hours. After the fifth failure the job is marked dead: no endless retries against an endpoint that is down, and the status is still on GET /v1/messages/:id.
Not open to the public yet.
We are onboarding a few teams at a time. Tell us what you send and where, and we will reach out when your workspace is ready. Your first request will be the one at the top of this page.
Join the waiting list