A practical guide
How to send a WhatsApp message from your code
One POST sends a text, an image, a video or a PDF to a WhatsApp number in any country. This page is the whole of it: the request, the number formats, files, and what comes back when somebody replies.
The request
Everything goes to the same endpoint. What you are sending is decided by which key you put in the body, so there is one shape to learn rather than six.
curl -X POST https://njiwa.upeo.ai/v1/messages \
-H "Authorization: Bearer sk_live_your_key" \
-d '{"to": "0712345678", "text": "Your order has shipped"}'
You get a message id back straight away, and the delivery updates arrive on your webhook as WhatsApp reports them.
{
"id": "msg_01J9ZK4Q7M2X8P0R3V6T5W1YHD",
"status": "sent",
"to": "254712345678",
"type": "text",
"created_at": "2026-08-31T09:14:02Z"
}
Phone numbers, in any format
Njiwa takes numbers the way they actually appear in a database rather than the way a specification wishes they did. All of these reach the same person:
+254 712 345 678, spaced and with a plus254712345678, bare international0712-345-678, local, punctuated0712345678, local, resolved against the country code of the number you are sending from
That last one is the useful part. A business in Lagos sending to
08012345678 reaches a Nigerian number, and a business in London
sending to 07700900123 reaches a British one, without either of
them normalising a database first. When Njiwa genuinely cannot tell which
country a local number belongs to, it says so and tells you what to send
instead, rather than guessing and delivering to a stranger.
Files, three ways
curl -X POST https://njiwa.upeo.ai/v1/messages \
-H "Authorization: Bearer sk_live_your_key" \
-F to=0712345678 \
-F document=@invoice-8841.pdf \
-F caption="Your invoice for August"
An invoice, a receipt, a ticket, a photograph. Attach it however suits what you already have:
- By URL. Point Njiwa at a file your system already serves and it fetches it.
- By upload. POST the bytes to
/v1/mediaand use themed_id you get back. - By stored id. Reuse a
med_id you uploaded earlier, so the same price list is uploaded once and sent a thousand times.
In every case Njiwa reads the real type from the file's own bytes rather
than trusting its name or its declared content type, so a PDF somebody
renamed .jpg still arrives as a PDF.
Getting replies back
Incoming messages and status changes are posted to an endpoint you nominate, signed with HMAC SHA-256 and timestamped, so your handler can prove a request came from us before it acts on it. Every attempt is logged with what we sent and what came back, which is what turns a failing integration into something you can read rather than guess at.
Retries that cannot double send
Send an Idempotency-Key header and a repeat of the same
request returns the original message id instead of sending a second message.
A timeout on your side stops being a decision about whether to risk
messaging a customer twice.
Works with what you already run
It is a plain HTTP API, so it fits a WooCommerce store, a Laravel application, an ERPNext instance, a Python script on a cron, or a Node service. Thin SDKs exist for PHP, Node and Python, and none of them are compulsory.
What it costs to try
Nothing. Every account gets a test key that validates every request, stores every message and fires real webhooks, and delivers nothing to WhatsApp. Build the whole integration before a single message leaves the building.