Skip to content
Sending & receiving

Receive messages

Kirim never polls for new messages — it pushes them to your webhook URL the moment Meta delivers them. Subscribe once; every inbound message and status update fans out as a signed POST.

Create a webhook subscription that listens for the inbound event families you care about:

Terminal window
curl -X POST https://api.kirim.chat/v1/webhook_subscriptions \
-H "Authorization: Bearer $KIRIM_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/webhooks/kirim",
"events": ["message.received", "message.status_updated"]
}'

The response carries initial_secret once — store it server-side so you can verify HMAC signatures on every incoming delivery. See Verifying signatures for the verification recipe.

Every event arrives as JSON with this envelope:

{
"id": "wbd_01HXYZ…",
"event": "message.received",
"delivered_at": "2026-05-26T09:30:00.000Z",
"data": {
"message": { /* see below */ }
}
}

The data.message body matches the shape of a Message resource you read via GET /v1/{phone_number_id}/messages/{id} — same fields, same types. Type-specific extras live under a key named after the message type:

Inbound typeExtra fields under data.message
textcontent (the body)
image / video / audio / document / stickermedia_url, mime_type, caption, filename
locationlocation.{latitude, longitude, name?, address?}
contactscontacts[].{name, phone?, wa_id?}
interactiveinteractive.{type, id, title, description?} — user tapped a button/list row
reactionreaction.{target_message_id, emoji} (empty emoji = unreact)
buttonbutton.{payload, text} — quick-reply tap on a template

See Webhook payloads for full examples per type.

Once you’ve persisted the inbound message, mark it as read so the sender’s double-tick turns blue:

import { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })
const phone = kirim.phoneNumbers(process.env.PHONE_ID!)
// `message.id` here is the Meta-native wamid from the webhook payload.
await phone.messages.markAsRead(message.id)

See Mark messages as read for the full Meta-style body shape and optional typing indicator.

  • Retries: Failed deliveries (non-2xx response, timeout, or network error) retry with exponential backoff up to ~24 hours. See Retries & auto-disable.
  • Replays: Inspect the dead-letter queue or re-deliver any failed event via the Webhook Deliveries API.
  • Ordering: Events for a single conversation arrive in causal order, but Kirim does not guarantee global ordering across conversations.