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": { … }}| Event | Source | Fires when |
|---|---|---|
customer.created | kirim | POST /v1/customers succeeds. |
customer.updated | kirim | PATCH /v1/customers/{id} changes any field. |
customer.archived | kirim | DELETE /v1/customers/{id} soft-deletes the row. |
customer.onboarded | kirim | End-customer completes Embedded Signup via a setup link. The business signal you usually want. |
customer.setup_link.created | kirim | A setup link is generated. |
customer.setup_link.consumed | kirim | A setup link is successfully used (paired with customer.onboarded). |
Subscribing
Section titled “Subscribing”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.
customer.created
Section titled “customer.created”Fires immediately after a successful POST /v1/customers (or dashboard
create, or MCP create_customer).
Payload
Section titled “Payload”{ "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" } }}Use it for
Section titled “Use it for”- 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.
customer.updated
Section titled “customer.updated”Fires when any of name, email, metadata, or status changes via
PATCH /v1/customers/{id}. Excludes archive — that gets its own
event.
Payload
Section titled “Payload”{ "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" } }}Use it for
Section titled “Use it for”- Re-syncing metadata into your CRM.
- Reacting to
status: 'suspended'transitions (your platform might pause automations until restoration).
customer.archived
Section titled “customer.archived”Fires when a customer is soft-archived via DELETE /v1/customers/{id}
or the dashboard “Archive” action.
Payload
Section titled “Payload”{ "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" } }}Use it for
Section titled “Use it for”- Marking the customer as inactive in your own database.
- Hiding their UI affordances in your operator dashboard.
customer.onboarded
Section titled “customer.onboarded”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.
Payload
Section titled “Payload”{ "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" }}Field reference
Section titled “Field reference”| Field | What it is |
|---|---|
customer_id | The public cus_… id. Match against your DB. |
account_id | Internal Kirimdev id for the WhatsApp account row. Use to disambiguate; not used for sending. |
phone_number_id | Meta’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_number | Display-formatted phone (+62 857-…). |
Use it for
Section titled “Use it for”- 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.
customer.setup_link.created
Section titled “customer.setup_link.created”Fires immediately after a successful
POST /v1/customers/{id}/setup_links.
Payload
Section titled “Payload”{ "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" } }}Use it for
Section titled “Use it for”- Audit logging of link generation events.
- Surfacing link analytics in your platform’s UI (“3 links generated this week”).
customer.setup_link.consumed
Section titled “customer.setup_link.consumed”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.
Payload
Section titled “Payload”{ "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" }}Use it for
Section titled “Use it for”- 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.
Pairing and ordering
Section titled “Pairing and ordering”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.
Suppressed cases
Section titled “Suppressed cases”| Scenario | Event fires? |
|---|---|
customer.updated with no changed fields | No |
| Re-creating an already-archived customer | No (archive is reversible via dashboard; the restore action emits customer.updated) |
Setup link revoked | No 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. |
Failure modes
Section titled “Failure modes”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 sameevt_…id. Dedupe on your side.
See also
Section titled “See also”- Customers — the lifecycle that triggers these events.
- Onboarding flow — where in the sequence each event fires.
- Webhooks / Overview — subscription model, fan-out semantics.