Webhooks
Webhooks let you receive HTTP POST callbacks the moment a connected human messages or tapbacks your agent, and as your agent's outbound messages move through their delivery lifecycle. iMessage events are delivered via the Webhook Subscriptions API — attach a subscription to the agent identity with the subset of imessage.* events you want.
The agent identity is the subscription owner whether its conversations use the shared service or an attached dedicated number. The identity is the stable resource the events belong to.
Event types
| Event | Description |
|---|---|
imessage.received | A connected human sent your agent a message |
imessage.reaction_received | A human tapbacked one of the thread's messages |
imessage.sent | An outbound message from your agent was accepted for delivery |
imessage.delivered | An outbound message reached the recipient's device |
imessage.delivery_failed | An outbound message failed to deliver — the payload carries the error fields |
Inbound traffic rejected by a contact-rule block or by default-block in whitelist mode emits no webhooks. Blocked messages are stored, and admin API keys or the Inkbox Console can audit them with is_blocked=true. Fan-out also pauses while the identity is paused or has imessage_enabled: false. Tapback removals do not fire an event; the next message read simply no longer includes the removed tapback.
Subscribing
Subscribe to any subset — add imessage.sent, imessage.delivered, and imessage.delivery_failed to the same subscription to track outbound delivery. See Webhook Subscriptions for listing, updating, and deleting subscriptions, and Signing keys for verifying that deliveries came from Inkbox.
Payload envelope
Every delivery POSTs a JSON envelope:
| Field | Type | Description |
|---|---|---|
id | string | Stable evt_... idempotency key for this event; unchanged across delivery retries |
event_type | string | One of the five imessage.* event types |
timestamp | string (ISO 8601) | When the event occurred |
data | object | The event payload — see below |
data carries message (for imessage.received and the delivery-lifecycle events) or reaction (for imessage.reaction_received), plus peer-resolution lists — and, on imessage.received when the subscription configures conversation context, an additive data.context block:
| Field | Type | Description |
|---|---|---|
data.message | object | null | The stored message on imessage.received, imessage.sent, imessage.delivered, and imessage.delivery_failed; null on reaction events |
data.reaction | object | null | The stored reaction on imessage.reaction_received; null on message events |
data.contacts | array | Organization-wide contact matches for the human's number, or the sender's number for an inbound group message. Always present, possibly empty — each entry has id, name, and memories (string[], possibly empty) |
data.agent_identities | array | Identity matches visible to the subscription's identity when that number belongs to another active agent identity in your org. Always present, possibly empty — each entry has id, agent_handle, and display_name |
imessage.received
The message shape matches the Message object, with one difference: webhook payloads carry no is_blocked field, because blocked messages never reach the webhook at all.
Group messages
An inbound group chat uses the same imessage.received event. Its message has is_group: true, null assignment_id, the sender's E.164 phone number in both sender_number and remote_number, and a best-known participant snapshot:
The participant snapshot is append-only best-known history, not authoritative current membership. A newly added member can be absent until later activity, and a member who leaves remains in the stored list.
imessage.reaction_received
reaction can be any of the seven named values ("love", "like", "dislike", "laugh", "emphasize", "question", or "eyes") or "custom" with the literal inbound emoji in custom_emoji. Remember the replacement rule: a new tapback from the same human on the same message replaces their previous one, so treat the latest event as the current state for that sender.
An inbound group tapback uses the same event and target-message semantics. Its reaction has assignment_id: null, target_message_id identifies the exact message bubble, and remote_number identifies the participant who reacted. Adds and replacements emit this event; a removal clears the live reaction from later message reads without emitting a removal webhook.
Delivery lifecycle events
Outbound messages fire an event on each message-level status transition: imessage.sent when the message is accepted for delivery, then imessage.delivered when it reaches the recipient's device, or imessage.delivery_failed if delivery fails. Only outbound messages produce these events, and only when the message as a whole changes status — per-recipient bookkeeping that doesn't move the message's own status does not fire.
A definitive rejection while the first message is still creating a new group can fail before the delivery lifecycle begins. Re-read that message and its conversation to observe status: "error" and group_creation_status: "not_created", then send another message using the same conversation_id to retry.
The three payloads share one shape and differ only in event_type, the message's status, and the error fields:
On imessage.sent the message arrives with status: "sent", on imessage.delivered with status: "delivered", both with the error fields null. On imessage.delivery_failed the message's status is "declined" or "error" and the error fields describe what went wrong.
For an outbound group event, assignment_id, remote_number, and sender_number are null, is_group is true, participants is the message's participant snapshot, and recipients contains one entry per participant. The webhook is a message-level transition, so the aggregate group state is applied to those participant entries together.
Delivery semantics
- Deliveries are fire-and-forget HTTP POSTs; your response status is logged but does not affect message processing. Respond
2xxquickly and process asynchronously. - Each subscription on the identity receives its own POST per event, independently.
- Verify deliveries with your signing key before trusting the payload.
Conversation context
When a subscription sets context_config, imessage.received payloads gain an additive data.context object keyed by the configured classes (email, texts, calls). The texts class is conversation-scoped (the triggering conversation, with any matching SMS merged in); email and calls resolve contact-scoped history through the matched contact. imessage.reaction_received and delivery-status events never carry context.
The block shape — per-class fields, item shapes, transcript abridgment, skip reasons, and the 256 KB cap — is documented on the Mail webhooks page, and the configuration shape on the Webhook Subscriptions page.