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.
Subscribe
Section titled “Subscribe”Create a webhook subscription that listens for the inbound event families you care about:
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"] }'import { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })
await kirim.webhookSubscriptions.create({ 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.
Inbound payload shape
Section titled “Inbound payload shape”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 type | Extra fields under data.message |
|---|---|
text | content (the body) |
image / video / audio / document / sticker | media_url, mime_type, caption, filename |
location | location.{latitude, longitude, name?, address?} |
contacts | contacts[].{name, phone?, wa_id?} |
interactive | interactive.{type, id, title, description?} — user tapped a button/list row |
reaction | reaction.{target_message_id, emoji} (empty emoji = unreact) |
button | button.{payload, text} — quick-reply tap on a template |
See Webhook payloads for full examples per type.
Acknowledging inbound
Section titled “Acknowledging inbound”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.
Reliability
Section titled “Reliability”- 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.