Skip to content

Webhooks for the inbox

Inbound messages reach you within seconds through your partner webhook. Three event types cover the inbox. Delivery, signing and retries are the same as for every other event; see Webhook setup.

A new DM from a contact. data carries the conversation as it is after the message, and the message itself.

{
"id": "evt2b4d6f8h1j3l",
"type": "message.received",
"created_at": "2026-08-25T10:02:18Z",
"partner": "acme",
"workspace": "ws7k2m9p4q1r8t3",
"external_id": "user_123",
"data": {
"conversation": {
"id": "cnv2b4d6f8h1j3l",
"workspace": "ws7k2m9p4q1r8t3",
"account": "acig4h2k5m7p9r1",
"network": "instagram",
"kind": "dm",
"platform_thread_id": "17847000000000001",
"contact": { "id": "5834920000000001", "name": "Dana de Vries", "handle": "dana.dv", "avatar_url": "https://scontent.example/dana.jpg" },
"post_platform_id": null,
"comment_id": null,
"last_message_at": "2026-08-25T10:02:17Z",
"last_message_preview": "Hi, do you have a slot on Friday afternoon?",
"unread": 1,
"status": "open",
"created": "2026-08-25T10:02:17Z"
},
"message": {
"id": "msg7q9s1u3w5y2a",
"conversation": "cnv2b4d6f8h1j3l",
"direction": "in",
"kind": "text",
"text": "Hi, do you have a slot on Friday afternoon?",
"media_url": null,
"platform_message_id": "m_AbCdEf0123456789",
"sent_by": "contact",
"status": "sent",
"created": "2026-08-25T10:02:17Z"
}
}
}

An image, video, audio or file attachment has the matching kind and a media_url. A tapped quick reply is kind: "postback" with the payload in text.

A new comment on a Page post or on Instagram media, or a reply inside an existing comment thread. Same shape, kind: "comment" on the conversation and on the message.

{
"id": "evt3c5e7g9i2k4m",
"type": "comment.received",
"created_at": "2026-08-25T11:40:03Z",
"partner": "acme",
"workspace": "ws7k2m9p4q1r8t3",
"external_id": "user_123",
"data": {
"conversation": {
"id": "cnv3c5e7g9i2k4m",
"workspace": "ws7k2m9p4q1r8t3",
"account": "acig4h2k5m7p9r1",
"network": "instagram",
"kind": "comment",
"platform_thread_id": "17990000000000001",
"contact": { "id": "5834920000000002", "name": "Sam K.", "handle": "sam.k", "avatar_url": null },
"post_platform_id": "17900000000000001",
"comment_id": "17990000000000001",
"last_message_at": "2026-08-25T11:40:02Z",
"last_message_preview": "How much is a cut?",
"unread": 1,
"status": "open",
"created": "2026-08-25T11:40:02Z"
},
"message": {
"id": "msg9s1u3w5y7a4c",
"conversation": "cnv3c5e7g9i2k4m",
"direction": "in",
"kind": "comment",
"text": "How much is a cut?",
"media_url": null,
"platform_message_id": "17990000000000001",
"sent_by": "contact",
"status": "sent",
"created": "2026-08-25T11:40:02Z"
}
}
}

Match post_platform_id against targets[].platform_post_id on your posts to show comments next to the post they belong to.

Fires for every outbound message, whichever surface sent it: your API call, the widget, the dashboard, an MCP client. Use it to mirror replies sent outside your own code.

{
"id": "evt4d6f8h1j3l5n",
"type": "message.sent",
"created_at": "2026-08-25T10:05:41Z",
"partner": "acme",
"workspace": "ws7k2m9p4q1r8t3",
"external_id": "user_123",
"data": {
"conversation": { "id": "cnv2b4d6f8h1j3l", "kind": "dm", "account": "acig4h2k5m7p9r1", "network": "instagram", "last_message_at": "2026-08-25T10:05:40Z", "unread": 1, "status": "open" },
"message": {
"id": "msg8r0t2v4x6z3b",
"conversation": "cnv2b4d6f8h1j3l",
"direction": "out",
"kind": "text",
"text": "Yes, Friday 15:00 or 16:30 is free. Which one?",
"media_url": null,
"platform_message_id": "m_XyZ0123456789abc",
"sent_by": "embed",
"status": "sent",
"created": "2026-08-25T10:05:40Z"
}
}
}

If you only ever reply through the API, you can ignore message.sent; you already have the message from the POST response.

  • Upsert the conversation by data.conversation.id on every event, then append the message by data.message.id. Both ids are stable.
  • De-duplicate on X-MissLess-Delivery. A retried delivery carries the same id.
  • Order is not guaranteed across events. Sort by message.created, and take last_message_at from the conversation rather than from the event you happened to receive last.
  • Mark read on your side by calling PATCH /v1/conversations/:id with { "unread": 0 } when staff opens the thread. The widget does the same, so the count stays consistent between your UI and the embedded inbox.
  • What you receive can contain personal data: names, handles, message text. Store it under your own legal basis and delete it when you no longer need it. See Data and privacy.