Skip to content
Reference

Webhooks

Six event types fire across the customer lifecycle. Each follows the standard Kirimdev-native event envelope:

{
"id": "evt_…",
"type": "customer.…",
"created_at": "2026-06-04T11:14:55Z",
"data": { }
}
EventSourceFires when
customer.createdkirimPOST /v1/customers succeeds.
customer.updatedkirimPATCH /v1/customers/{id} changes any field.
customer.archivedkirimDELETE /v1/customers/{id} soft-deletes the row.
customer.onboardedkirimEnd-customer completes Embedded Signup via a setup link. The business signal you usually want.
customer.setup_link.createdkirimA setup link is generated.
customer.setup_link.consumedkirimA setup link is successfully used (paired with customer.onboarded).
await kirim.webhookSubscriptions.create({
url: 'https://yourapp.com/webhooks/kirim',
events: [
'customer.created',
'customer.updated',
'customer.archived',
'customer.onboarded',
'customer.setup_link.created',
'customer.setup_link.consumed',
],
})

You can subscribe to any subset. Most platforms want at minimum customer.onboarded (when a tenant completes signup) and customer.archived (to update local UI).

Every delivery carries a signed X-Kirim-Signature-256 header. Verify it before trusting the payload — see Signing.


Fires immediately after a successful POST /v1/customers (or dashboard create, or MCP create_customer).

{
"id": "evt_01HXYZABCDEFGHJKMNPQRSTVWX",
"type": "customer.created",
"created_at": "2026-06-04T10:00:00.000Z",
"data": {
"customer": {
"id": "cus_335T08RM0EAKN9DTE6RD5RWP7B",
"object": "customer",
"name": "Acme Logistics",
"email": "admin@acme.io",
"status": "pending",
"metadata": { "crm_id": "C-1234", "branch": "Jakarta" },
"archived_at": null,
"team_id": "team_internal_id",
"created_at": "2026-06-04T10:00:00.000Z",
"updated_at": "2026-06-04T10:00:00.000Z"
}
}
}
  • Mirroring the new customer into your CRM.
  • Surfacing an “Acme Logistics is now on the platform” toast in your operator dashboard.

The customer starts in status: 'pending'. Wait for customer.onboarded before assuming they can send messages.


Fires when any of name, email, metadata, or status changes via PATCH /v1/customers/{id}. Excludes archive — that gets its own event.

{
"id": "evt_01HXYZ…",
"type": "customer.updated",
"created_at": "2026-06-04T10:30:00.000Z",
"data": {
"customer": {
"id": "cus_335T08…",
"object": "customer",
"name": "Acme Logistics",
"email": "ops@acme.io",
"status": "active",
"metadata": { "crm_id": "C-1234", "branch": "Jakarta", "segment": "premium" },
"archived_at": null,
"team_id": "team_internal_id",
"created_at": "2026-06-04T10:00:00.000Z",
"updated_at": "2026-06-04T10:30:00.000Z"
}
}
}
  • Re-syncing metadata into your CRM.
  • Reacting to status: 'suspended' transitions (your platform might pause automations until restoration).

Fires when a customer is soft-archived via DELETE /v1/customers/{id} or the dashboard “Archive” action.

{
"id": "evt_01HXYZ…",
"type": "customer.archived",
"created_at": "2026-06-04T11:00:00.000Z",
"data": {
"customer": {
"id": "cus_335T08…",
"object": "customer",
"name": "Acme Logistics",
"email": "admin@acme.io",
"status": "archived",
"metadata": { "crm_id": "C-1234" },
"archived_at": "2026-06-04T11:00:00.000Z",
"team_id": "team_internal_id",
"created_at": "2026-06-04T10:00:00.000Z",
"updated_at": "2026-06-04T11:00:00.000Z"
}
}
}
  • Marking the customer as inactive in your own database.
  • Hiding their UI affordances in your operator dashboard.

The business signal for most platform integrations. Fires when an end-customer completes Meta Embedded Signup through a setup link and the WhatsApp account is attached.

{
"id": "evt_01HXYZ…",
"type": "customer.onboarded",
"created_at": "2026-06-04T11:14:55.000Z",
"data": {
"customer_id": "cus_335T08RM0EAKN9DTE6RD5RWP7B",
"account_id": "internal_wa_account_id_xxx",
"phone_number_id": "1111475158712095",
"phone_number": "+62 857-2516-5424"
}
}
FieldWhat it is
customer_idThe public cus_… id. Match against your DB.
account_idInternal Kirimdev id for the WhatsApp account row. Use to disambiguate; not used for sending.
phone_number_idMeta’s business_phone_number_id. This is the value you plug into POST /v1/{phone_number_id}/messages to send on the new account.
phone_numberDisplay-formatted phone (+62 857-…).
  • Updating your CRM with the customer’s WhatsApp number.
  • Triggering a “Welcome — your WhatsApp is connected” automation.
  • Surfacing the new number in your operator dashboard.

Fires immediately after a successful POST /v1/customers/{id}/setup_links.

{
"id": "evt_01HXYZ…",
"type": "customer.setup_link.created",
"created_at": "2026-06-04T10:05:00.000Z",
"data": {
"customer_id": "cus_335T08…",
"setup_link": {
"id": "csl_GW4TD55B35VNGXG8SCV3P4Q9JB",
"object": "customer_setup_link",
"customer_id": "internal_cus_id",
"status": "active",
"token_last4": "Wvqb",
"expires_at": "2026-06-11T10:05:00.000Z",
"consumed_at": null,
"success_redirect_url": "https://yourapp.com/onboarded",
"failure_redirect_url": "https://yourapp.com/onboard-failed",
"created_at": "2026-06-04T10:05:00.000Z"
}
}
}
  • Audit logging of link generation events.
  • Surfacing link analytics in your platform’s UI (“3 links generated this week”).

Fires when an end-customer successfully completes Embedded Signup through a setup link. Always paired with customer.onboarded — the two events fire within the same handler.

{
"id": "evt_01HXYZ…",
"type": "customer.setup_link.consumed",
"created_at": "2026-06-04T11:14:55.000Z",
"data": {
"customer_id": "cus_335T08…",
"setup_link": {
"id": "csl_GW4TD…",
"object": "customer_setup_link",
"customer_id": "internal_cus_id",
"status": "consumed",
"token_last4": "Wvqb",
"expires_at": "2026-06-11T10:05:00.000Z",
"consumed_at": "2026-06-04T11:14:55.000Z",
"success_redirect_url": "https://yourapp.com/onboarded",
"failure_redirect_url": "https://yourapp.com/onboard-failed",
"created_at": "2026-06-04T10:05:00.000Z"
},
"account_id": "internal_wa_account_id_xxx"
}
}
  • Marking the link as used in your own database.
  • Closing the audit trail on a tenant’s onboarding journey (“Link created → emailed → consumed at 11:14 UTC”).

For the business signal of “the tenant is connected”, prefer customer.onboarded — it carries the phone_number_id you need to send messages.


customer.setup_link.consumed and customer.onboarded fire in quick succession (same handler, sequential publishes). They share the same customer_id and account_id values, so your handler can join them if needed.

Webhook delivery order between events is not guaranteed at the HTTP layer — they may arrive in either order at your endpoint. Branch on type and store both before reconciling.

customer.created always fires before any setup link events for that customer. customer.archived always fires after.

ScenarioEvent fires?
customer.updated with no changed fieldsNo
Re-creating an already-archived customerNo (archive is reversible via dashboard; the restore action emits customer.updated)
Setup link revokedNo dedicated event; the underlying customer_setup_links row state transition is observable via the link list endpoint.
Setup link expired (lazy flip on read)No dedicated event; same as above.

Webhook delivery follows the standard Kirimdev rules:

  • Subscriptions auto-pause after consecutive failures. See Retries.
  • HMAC signature on every delivery. See Signing.
  • Idempotent on event_id — retries carry the same evt_… id. Dedupe on your side.