> ## Documentation Index
> Fetch the complete documentation index at: https://docs.my-aichatbot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Event reference

> Every event your webhook can receive, with its full payload.

Six event types can reach your webhook. Route on the `X-MyAIChatbot-Event` header or the body's `event` field — they always match.

| Event                                                       | Fires when                           | Has `response_url` |
| ----------------------------------------------------------- | ------------------------------------ | ------------------ |
| [`message.created`](#message-created)                       | Someone sends a message              | Yes                |
| [`interaction.button_clicked`](#interaction-button-clicked) | A button is clicked                  | Yes                |
| [`interaction.form_submitted`](#interaction-form-submitted) | A form is sent in                    | Yes                |
| [`conversation.handoff`](#conversation-handoff)             | An operator sends you the transcript | No                 |
| [`webhook.verify`](#webhook-verify)                         | You press **Verify**                 | No                 |
| [`webhook.test`](#webhook-test)                             | You press **Test webhook**           | No                 |

Every payload carries `api_version` (currently `"2026-07-01"`) and a true/false `test`.

***

<h2 id="message-created">
  message.created
</h2>

The main event. A visitor, member, or operator said something.

```json theme={null}
{
  "api_version": "2026-07-01",
  "block_kit_version": "1",
  "event": "message.created",
  "event_id": "evt_01J9ZW9K7QW5T3",
  "created_at": "2026-07-17T03:15:12.412Z",
  "test": false,
  "workspace_id": "b6a2",
  "chatbot": { "id": "c1d4", "name": "Acme Support Bot" },
  "conversation": {
    "id": "9f31",
    "channel": "widget",
    "routing_state": "bot",
    "metadata": { "plan": "pro" }
  },
  "message": {
    "id": "m_77ab",
    "seq": 14,
    "sender_type": "visitor",
    "text": "Where is my order #1042?",
    "blocks": [{ "type": "text", "text": "Where is my order #1042?" }],
    "created_at": "2026-07-17T03:15:12.398Z"
  },
  "response_url": "https://app.my-aichatbot.com/api/v1/responses/eyJ2Ijox",
  "response_url_expires_at": "2026-07-17T04:00:12.412Z"
}
```

<ResponseField name="conversation.channel" type="string">
  `widget`, `public_page`, or `internal`.
</ResponseField>

<ResponseField name="conversation.routing_state" type="string">
  `bot` or `taken_over`. Once it is `taken_over`, you cannot post into this conversation.
</ResponseField>

<ResponseField name="conversation.metadata" type="object">
  Answers the visitor gave before the chat started, keyed by field name. Empty if you collect none.
</ResponseField>

<ResponseField name="message.seq" type="number">
  A per-conversation counter that only goes up. Sort by this, not by arrival time.
</ResponseField>

<ResponseField name="message.sender_type" type="string">
  `visitor`, `member`, `bot`, `operator`, or `system`.
</ResponseField>

<ResponseField name="message.text" type="string">
  The blocks flattened into plain text. Handy, and usually all you need.
</ResponseField>

<ResponseField name="response_url" type="string | null">
  Where to POST your reply. `null` if the platform is set up wrong.
</ResponseField>

***

<h2 id="interaction-button-clicked">
  interaction.button\_clicked
</h2>

A flat payload with `action_id`, `value`, and `label` at the top level. See [Interactions](/block-kit/interactions) for the full details.

```json theme={null}
{
  "api_version": "2026-07-01",
  "block_kit_version": "1",
  "event": "interaction.button_clicked",
  "type": "interaction.button_clicked",
  "event_id": "evt_01JZYA7M3T",
  "chatbot_id": "c1d4",
  "conversation_id": "9f31",
  "message_id": "m_77ab",
  "block_id": "blk_x8f2",
  "action_id": "approve",
  "value": "req_991",
  "label": "Approve",
  "message_metadata": { "order_id": "4821" },
  "user": { "kind": "visitor", "id": "vs_8c2", "display_name": "Visitor 4821" },
  "clicked_at": "2026-07-17T03:12:44.120Z",
  "response_url": "https://app.my-aichatbot.com/api/v1/responses/eyJ2Ijox",
  "delivery_attempt": 1
}
```

<Note>
  `event` and `type` carry the same dotted name. `type` is there for tools that cannot map nested fields.
</Note>

***

<h2 id="interaction-form-submitted">
  interaction.form\_submitted
</h2>

```json theme={null}
{
  "api_version": "2026-07-01",
  "block_kit_version": "1",
  "event": "interaction.form_submitted",
  "type": "interaction.form_submitted",
  "event_id": "evt_01JZYB2N8Q",
  "chatbot_id": "c1d4",
  "conversation_id": "9f31",
  "message_id": "m_55cd",
  "block_id": "blk_p2m9",
  "action_id": "callback",
  "form_id": "callback",
  "values": { "full_name": "Priya Sharma", "phone": "+61 400 000 000", "time": "Morning" },
  "message_metadata": { "flow": "callback_request" },
  "user": { "kind": "visitor", "id": "vs_8c2", "display_name": "Visitor 4821" },
  "submitted_at": "2026-07-17T03:15:02.551Z",
  "response_url": "https://app.my-aichatbot.com/api/v1/responses/eyJ2Ijox",
  "delivery_attempt": 1
}
```

<Note>
  `form_id` is the form's id — the one you set, or the one we made if you left it out. See [Interactions](/block-kit/interactions) for how `values` are typed.
</Note>

***

<h2 id="conversation-handoff">
  conversation.handoff
</h2>

An operator took over and pressed **Send transcript to backend**. It exists so your CRM can catch up. There is **no `response_url`** — on purpose, because the bot cannot speak in a conversation a human now owns.

```json theme={null}
{
  "api_version": "2026-07-01",
  "block_kit_version": "1",
  "event": "conversation.handoff",
  "event_id": "evt_01JA01",
  "created_at": "2026-07-17T03:41:07.000Z",
  "test": false,
  "workspace_id": "b6a2",
  "chatbot": { "id": "c1d4", "name": "Acme Support Bot" },
  "conversation": { "id": "9f31", "routing_state": "taken_over" },
  "taken_over": { "by": { "id": "u_12", "name": "Bryan" }, "at": "2026-07-17T03:20:00.000Z" },
  "slice_from": "2026-07-17T03:20:00.000Z",
  "slice_to": "2026-07-17T03:41:07.000Z",
  "transcript": [
    { "sender_type": "system", "sender_name": null, "text": "Bryan joined the conversation", "created_at": "2026-07-17T03:20:00.000Z" },
    { "sender_type": "visitor", "sender_name": null, "text": "ok, a human, finally", "created_at": "2026-07-17T03:20:31.000Z" },
    { "sender_type": "operator", "sender_name": "Bryan", "text": "Hi! I refunded order 1042.", "created_at": "2026-07-17T03:21:05.000Z" }
  ],
  "response_url": null
}
```

<ResponseField name="transcript" type="array">
  Every message from `slice_from` on — the human era only, flattened to plain text.
</ResponseField>

The button can be pressed again. Each press re-sends the whole slice with a fresh `event_id`, so compare `slice_from` and `slice_to`.

***

<h2 id="webhook-verify">
  webhook.verify
</h2>

Sent when you press **Verify**. Send the `challenge` back. Never retried, and has no `response_url`. See [Webhooks](/receiving-events/webhooks#verification-is-required).

```json theme={null}
{
  "api_version": "2026-07-01",
  "event": "webhook.verify",
  "event_id": "evt_01J9",
  "created_at": "2026-07-17T02:11:09.412Z",
  "test": true,
  "workspace_id": "b6a2",
  "chatbot": { "id": "c1d4", "name": "Acme Support Bot" },
  "challenge": "3f9a8c2e",
  "verification_code": "XXXX-XXXX",
  "response_url": null
}
```

***

<h2 id="webhook-test">
  webhook.test
</h2>

Sent by the **Test webhook** button. Shaped just like `message.created` so your real handler runs, but with a fake conversation, `test: true`, and no `response_url`.

```json theme={null}
{
  "api_version": "2026-07-01",
  "event": "webhook.test",
  "event_id": "evt_01J9",
  "test": true,
  "chatbot": { "id": "c1d4", "name": "Acme Support Bot" },
  "message": {
    "text": "This is a test event from your bot's delivery log.",
    "blocks": [{ "type": "text", "text": "This is a test event from your bot's delivery log." }]
  },
  "response_url": null,
  "response_url_expires_at": null
}
```

Whatever you return is parsed and shown back to you — including any problems — without touching a real conversation.

<Tip>
  Guard anything you cannot undo behind `if (event.test) return`. Both `webhook.verify` and `webhook.test` set it.
</Tip>
