Webhook event reference

Pre-release v1, published 2026-09-14. This page commits to the shape of the API, not to a date. We will build exactly what is documented here. The shape can still change until 2026-10-14; after that, changes follow Versioning and stability.

The banner comes off one page at a time as each route goes live. While it is here, build against the contract and assume the route is not callable yet.

Every event below is also in the API Reference, under Webhook events, where each variant is selectable from a dropdown next to the payload.

Delivery, signature verification and retries: Webhook events.

Every event shares the same envelope; only data differs.

message

The envelope is the same on every channel. What differs is inside data: Instagram carries a source and an Instagram sender, Telegram carries a Telegram user id and no source.

{
"schema_version": "1.0.0",
"event": "message",
"delivery_id": "dlv_9fK2mQ",
"occurred_at": "2026-09-20T11:09:02Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_1aB2cD",
"external_id": "cli_msg_1",
"source": "direct",
"role": "client",
"type": "text",
"text": "Hi, do you have these roses in stock?",
"markdown_content": null,
"reply_to_id": null,
"form_data": null,
"attachments": [],
"is_edited": false,
"sender": { "id": "iguser_88213", "username": "sara.k" },
"created_at": "2026-09-20T11:09:02Z"
}
}
FieldNotes
roleclient, ai, or operator. sender is null for ai and operator
typetext · voice · image · video · multimedia · document · poll · form · template
sourceInstagram only: direct · comment · story_reply · story_mention · reel
external_idYour idempotency key
reply_to_idThe message_id this replies to, or null
markdown_contentRich rendering; text stays plain. Either may be null
form_dataPresent when type is form

Triggered by a comment, with an attachment

{
"schema_version": "1.0.0",
"event": "message",
"delivery_id": "dlv_2xK8pL",
"occurred_at": "2026-09-20T11:14:20Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_7mN2qS",
"message_id": "msg_5eF6gH",
"source": "comment",
"media_id": "17895695668004550",
"role": "client",
"type": "image",
"text": "",
"attachments": [
{ "type": "image", "url": "https://cdn.parstechai.com/m/9fK2mQ.jpg", "mime": "image/jpeg" }
],
"sender": { "id": "iguser_44190", "username": "ali.m" },
"created_at": "2026-09-20T11:14:20Z"
}
}

media_id appears only when the event originated from a post or reel.

A voice message

Attachments arrive as ordinary message events with a non-text type, never as a separate event. text is empty and the payload is in attachments.

{
"schema_version": "1.0.0",
"event": "message",
"delivery_id": "dlv_6vB3nK",
"occurred_at": "2026-09-20T11:31:12Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_2vC5xN",
"source": "direct",
"role": "client",
"type": "voice",
"text": "",
"attachments": [
{
"type": "voice",
"url": "https://cdn.parstechai.com/m/2vC5xN.ogg",
"mime": "audio/ogg",
"duration_seconds": 14
}
],
"sender": { "id": "iguser_88213", "username": "sara.k" },
"created_at": "2026-09-20T11:31:12Z"
}
}

Attachment URLs are time-limited. Download the file when the event arrives rather than storing the URL and fetching it later.

A form submission

{
"schema_version": "1.0.0",
"event": "message",
"delivery_id": "dlv_4jH7sV",
"occurred_at": "2026-09-20T11:40:00Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_8kP1rT",
"role": "client",
"type": "form",
"text": null,
"form_data": [
{ "type": "text", "situation": "question", "key": "Your name", "value": "Sara" },
{ "type": "single_select", "situation": "rate", "key": "Service rating", "value": "excellent" }
],
"attachments": [],
"created_at": "2026-09-20T11:40:00Z"
}
}

message.generating

The AI has started composing a reply. Show a typing indicator.

{
"schema_version": "1.0.0",
"event": "message.generating",
"delivery_id": "dlv_5kL3nP",
"occurred_at": "2026-09-20T11:09:03Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": { "conversation_id": "cnv_4dR8nW" }
}

Hide the indicator when the matching message arrives. There is no explicit “stopped” event. A generation that fails produces no message, so treat it as a timeout after a few seconds.


chat

A conversation changed state. status is one of answered, operator_attention, not_answered, closed, or pending, and previous_status is what it was before.

Each one means something different for your side, so handle them separately rather than storing the string.

The AI or an operator replied. Nothing is waiting on anyone.

{
"schema_version": "1.0.0",
"event": "chat",
"delivery_id": "dlv_6hJ4kM",
"occurred_at": "2026-09-20T11:09:08Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"status": "answered",
"previous_status": "not_answered",
"changed_at": "2026-09-20T11:09:08Z"
}
}

Closing starts a fresh context

When a chat closes, the prior history stops being sent as context to the AI. A later conversation with the same end user starts clean.

context_reset is not a deletion flag. Nothing is deleted. The messages remain, they simply stop being replayed into the next conversation’s context. A partner who treated this as a deletion instruction would discard a transcript we still hold.

Your stored transcriptUnaffected. Keep it
Reading the conversation afterwardsStill works
The next conversation with that userStarts with no prior context

reaction

Someone added or removed a reaction on a message. action tells you which, so the same handler covers both: apply it on added, and undo it on removed.

{
"schema_version": "1.0.0",
"event": "reaction",
"delivery_id": "dlv_3pQ9rT",
"occurred_at": "2026-09-20T11:22:05Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_1aB2cD",
"action": "added",
"emoji": "❤️",
"by": { "id": "iguser_88213", "username": "sara.k" }
}
}

is_typing

A human started or stopped typing. This is distinct from message.generating, which is the AI. You may want to render them differently: “Sara is typing” against “generating a reply”.

{
"schema_version": "1.0.0",
"event": "is_typing",
"delivery_id": "dlv_7rS2tU",
"occurred_at": "2026-09-20T11:25:10Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": {
"conversation_id": "cnv_4dR8nW",
"is_typing": true,
"by": { "id": "iguser_88213", "username": "sara.k", "role": "client" }
}
}

A stopped event is not guaranteed. A connection can drop mid-typing, so expire the indicator yourself after a few seconds rather than waiting for is_typing: false.