kenari.dev

Sending messages#

With kenari you send messages the same way you would with Meta's Cloud API. Use the same path and the same JSON body. Only the host and the token change.

http
POST https://api.kenari.dev/{version}/{phone_number_id}/messagesAuthorization: Bearer kn_live_…Content-Type: application/json
  • {version} is a Graph API version such as v21.0. It's sent to Meta exactly as you wrote it.
  • {phone_number_id} is the Meta phone number id of a number connected to your kenari account. You can copy it from the Numbers page in the dashboard.
  • Authorization holds your kenari API key, not a Meta token. kenari adds the Meta token for you. See Authentication.

What kenari changes#

Very little. kenari:

  • checks your key, your billing, and that the number belongs to your account
  • replaces your Authorization header with the number's Meta access token and adds appsecret_proof to the query string
  • sends your body to Meta byte for byte. It's never parsed, reformatted or re-ordered.
  • returns Meta's status and body unchanged, plus the RateLimit-* and X-Kenari-Request-Id headers

The full list of what's forwarded is on Supported endpoints.

Sends count against the number's per-second rate limit. kenari waits up to 30 seconds for Meta's response.

Text#

bash
curl -X POST "https://api.kenari.dev/v21.0/$PHONE_NUMBER_ID/messages" \  -H "Authorization: Bearer $KENARI_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "messaging_product": "whatsapp",    "to": "6281234567890",    "type": "text",    "text": { "body": "Hello from kenari" }  }'

The response is Meta's response, unchanged:

json
{  "messaging_product": "whatsapp",  "contacts": [{ "input": "6281234567890", "wa_id": "6281234567890" }],  "messages": [{ "id": "wamid.…" }]}

Media#

Send media by link, or by an id returned from uploading through kenari:

json
{  "messaging_product": "whatsapp",  "to": "6281234567890",  "type": "image",  "image": { "id": "<media id>", "caption": "Your receipt" }}

Interactive#

Buttons, lists and the other interactive types all go to the same path:

json
{  "messaging_product": "whatsapp",  "to": "6281234567890",  "type": "interactive",  "interactive": {    "type": "button",    "body": { "text": "Confirm your booking?" },    "action": {      "buttons": [        { "type": "reply", "reply": { "id": "yes", "title": "Yes" } },        { "type": "reply", "reply": { "id": "no", "title": "No" } }      ]    }  }}

Templates#

Outside the 24-hour customer service window you have to use an approved template. Managing templates is covered on Templates. To send one:

json
{  "messaging_product": "whatsapp",  "to": "6281234567890",  "type": "template",  "template": { "name": "order_shipped", "language": { "code": "en" } }}

Reactions#

json
{  "messaging_product": "whatsapp",  "to": "6281234567890",  "type": "reaction",  "reaction": { "message_id": "wamid.…", "emoji": "👍" }}

Mark as read#

Mark as read uses the same messages path:

json
{  "messaging_product": "whatsapp",  "status": "read",  "message_id": "wamid.…"}

The messages edge accepts POST and PUT, and both are forwarded to Meta unchanged.

Test keys#

A kn_test_… key only works with numbers that Meta marks as SANDBOX (Meta test numbers). Used on any other number, it gets a 401 with Test keys only work with sandbox numbers., so a test key can never send a real message.

Errors#

A send can fail in kenari before it reaches Meta. The causes are an unknown number (404), a number that is disconnected, restricted, flagged or unregistered (422), the rate limit (429), or Meta being unreachable (502/503). kenari never retries a send. See Errors.