Sending Messages
Two endpoints send messages. POST /api/v1/waba/messages/template is a shortcut for the common case; POST /api/v1/waba/messages sends anything.
Send an approved template
curl -X POST https://core-prod.malipopay.co.tz/api/v1/waba/messages/template \
-H "apiToken: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "255712345678",
"templateName": "order_confirmation",
"language": "en",
"variables": { "1": "John", "2": "50,000", "3": "ORD-001" }
}'
| Field | Required | Notes |
|---|---|---|
to | Yes | E.164, 8 to 15 digits, leading + optional |
templateName | Yes | Must be APPROVED |
language | Yes | Must match the approved template exactly: en and en_US are different templates |
variables | No | Positional placeholders keyed "1", "2", … |
phoneNumberId | No | Send from a specific connected number |
Send anything
POST /api/v1/waba/messages takes a type and a matching body object. The object named by type must be present.
curl -X POST https://core-prod.malipopay.co.tz/api/v1/waba/messages \
-H "apiToken: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "255712345678",
"type": "text",
"text": { "body": "Your order is out for delivery." }
}'
Supported types: text, template, image, document, audio, video, sticker, location, contacts, interactive, reaction.
A text send only reaches the customer if a 24-hour window is already open with them. Outside a window, use type: "template". MALIPOPAY will accept and bill the send either way; Meta is what drops it.
Media
Reference a public URL with link, or upload once and reuse the returned id:
{
"to": "255712345678",
"type": "image",
"image": { "link": "https://example.co.tz/receipt.png", "caption": "Your receipt" }
}
{
"to": "255712345678",
"type": "document",
"document": { "id": "1234567890", "filename": "invoice.pdf" }
}
Location
{
"to": "255712345678",
"type": "location",
"location": { "latitude": -6.7924, "longitude": 39.2083, "name": "Sinza Lion", "address": "Dar es Salaam" }
}
Reaction
message_id is the wamid you are reacting to. An empty emoji removes the reaction, which is why it is allowed to be empty:
{ "to": "255712345678", "type": "reaction", "reaction": { "message_id": "wamid.HBg...", "emoji": "👍" } }
Choosing the sender number
| Field | Purpose |
|---|---|
phoneNumberId | Send from a specific number you have connected |
sendMode | merchant-then-platform (default) uses your number and falls back to the MALIPOPAY platform number; merchant-only fails if you have no number of your own; platform-only always uses the platform number |
Use merchant-only when it matters that the message comes from your brand and you would rather it fail loudly than go out under someone else's name.
Conversation category
conversationCategory overrides what the send is billed as. Leave it alone unless you have a reason: a template is billed at its own category, and anything else is billed SERVICE when a window is open and UTILITY when it is not.
Upload media
curl -X POST https://core-prod.malipopay.co.tz/api/v1/waba/media \
-H "apiToken: YOUR_API_KEY" \
-F "file=@receipt.png"
Returns a media id for use as image.id, document.id, video.id or audio.id.
A media id is scoped to the number that uploaded it and expires after 30 days on Meta's side. Upload under the same phoneNumberId you intend to send from, or the send fails.
Bulk send
POST /api/v1/waba/messages/bulk is a multipart upload: a CSV as field file with a to column, one recipient per row, plus the shared message as flat form fields (type, text_body, or template_name + template_language, media_link, conversationCategory).
curl -X POST https://core-prod.malipopay.co.tz/api/v1/waba/messages/bulk \
-H "apiToken: YOUR_API_KEY" \
-F "file=@recipients.csv" \
-F "type=template" \
-F "template_name=order_confirmation" \
-F "template_language=en"
Returns 202 with a batchId, the queued count, and any rows it could not parse. Each recipient is queued individually, so a bad row does not stop the batch.
Scan the list before you send. Junk numbers such as 255777777777 pass the format check, and failed deliveries to dead numbers damage your Meta quality rating, which throttles your throughput.
Responses
202 means accepted and dispatched to Meta, not delivered. Delivery arrives on your webhook.
400 covers validation failures, an insufficient wallet balance, a template that is not approved, and errors Meta returned. The message says which. If Meta rejects the send, the wallet charge is refunded automatically.
Reading what you sent
curl "https://core-prod.malipopay.co.tz/api/v1/waba/messages?direction=OUT&limit=50" \
-H "apiToken: YOUR_API_KEY"
Filter by direction (IN / OUT) and status, page with skip / limit / page. GET /api/v1/waba/messages/export returns the same data as CSV.