Receive messages
Kirimdev 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.kirimdev.com/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"] }'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'],})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”message.received and message.status are Meta passthrough
events — Kirimdev forwards the exact JSON Meta sent. The top-level
body is always Meta’s envelope:
{ "object": "whatsapp_business_account", "entry": [ { "id": "<WABA_ID>", "changes": [ { "field": "messages", "value": { /* Meta value object */ } } ] } ]}Branch on the X-Kirim-Event request header to decide what’s inside
entry[0].changes[0].value:
X-Kirim-Event: message.received→value.messages[]carries the inbound message.value.metadata.phone_number_idis your connected number;messages[0].fromis the customer’s WhatsApp id.X-Kirim-Event: message.status→value.statuses[]carries an outbound delivery update.statuses[0].idis thewamidof the message you sent.
messages[0].type selects the type-specific subobject. The shapes
below are Meta’s, not Kirimdev’s — see
Meta’s WhatsApp Cloud API webhook reference:
Inbound messages[0].type | Subobject |
|---|---|
text | text.body |
image / video / audio / document / sticker | {type}.{id, mime_type, sha256, caption?, filename?} — fetch bytes via GET /v1/{phone_number_id}/messages/{wamid}/media |
location | location.{latitude, longitude, name?, address?} |
contacts | contacts[] (Meta-shaped) |
interactive | interactive.{type, button_reply?, list_reply?} |
reaction | reaction.{message_id, emoji} (empty emoji = unreact) |
button | button.{payload, text} — quick-reply tap on a template |
Kirimdev-native events (conversation.assigned, conversation.closed,
contact.created, contact.updated) use a different envelope —
{ id, type, created_at, data }. See
Event catalogue for those.
See Webhook payloads for full copy-paste fixtures per event 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!)
// `wamid` is the Meta-native id from// body.entry[0].changes[0].value.messages[0].idawait phone.messages.markAsRead(wamid)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 Webhooks overview —
every subscription exposes a
/deliveriescollection plusPOST /replayandPOST /bulk_replayactions. - Ordering: Events for a single conversation arrive in causal order, but Kirimdev does not guarantee global ordering across conversations.