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

# Support webhooks

> Receive an HTTP request when a message arrives, a ticket changes, or a knowledge base article is created.

A webhook sends a `POST` request to your URL each time a chosen event happens in your organization: a customer writes in, a ticket is created or closed, an agent changes its status. Use webhooks to mirror tickets into your CRM, alert your team, or relay the AI agent's replies to a channel you run.

Manage webhooks in **Settings > Webhooks**, or with the API below. Every request goes to `https://back.leap.onclarity.com` with `Authorization: Bearer $CLARITY_API_KEY` (see [Authentication](/authentication.md)).

> [!NOTE]
>
> These webhooks cover chat, email, and ticket events. For a summary of each finished voice call, use the Voice API's [call-ended webhook](/voice/call-ended-webhook.md).

## Set up a webhook

**1. Find the event ids**

`GET /api/webhooks/events/list` returns the events your organization can subscribe to. Each has a numeric `id`, a `key` (for example `message`), and a `display_name`.

```bash theme={null}
curl "https://back.leap.onclarity.com/api/webhooks/events/list?limit=100" \
  -H "Authorization: Bearer $CLARITY_API_KEY"
```

```json theme={null}
{
  "list": [
    {
      "id": 21,
      "key": "kb_article_created",
      "display_name": "KB Article Created",
      "specs": { "description": "Triggered when a Knowledge Base article is created" }
    }
  ],
  "total": 1
}
```

`specs` is free-form information about the event, such as a description. Pass `specs=false` to get `specs` as `null`.

The ids differ between environments, so look them up rather than hard-coding them. The list pages with `limit` (default 20) and `offset`.

**2. Create the webhook**

```bash theme={null}
curl -X POST https://back.leap.onclarity.com/api/webhooks/create \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://hooks.example.com/clarity",
    "event_ids": [21],
    "status": "active"
  }'
```

**endpoint** `string` *required*

The `http` or `https` URL Clarity posts to.

**event\_ids** `integer[]` (default: \[])

The events to send. An unknown id answers `404 webhook_event_not_found`.

**status** `string` (default: active)

`active` or `disabled`. Only active webhooks receive events.

The response is the webhook:

```json theme={null}
{
  "id": 14,
  "organization_id": 42,
  "endpoint": "https://hooks.example.com/clarity",
  "status": "active",
  "created_at": "2026-10-22T10:30:00.000000Z",
  "updated_at": "2026-10-22T10:30:00.000000Z",
  "events": [
    {
      "id": 21,
      "key": "kb_article_created",
      "display_name": "KB Article Created",
      "specs": { "description": "Triggered when a Knowledge Base article is created" }
    }
  ]
}
```

**3. Handle the requests**

Answer each request with a `2xx` status within 30 seconds. See [Delivery](#delivery).

### Manage webhooks

| Endpoint | Does |
| - | - |
| `GET /api/webhooks/list?limit=20&offset=0` | List your webhooks: `{"list": [...], "total": N}` |
| `GET /api/webhooks/{webhook_id}` | Read one webhook |
| `POST /api/webhooks/edit` | Change `endpoint`, `event_ids`, or `status`. Send `webhook_id` and only the fields to change; `event_ids` replaces the whole list. |
| `DELETE /api/webhooks/{webhook_id}` | Delete a webhook. Answers `{"status": "ok", "data": {}}`. |

```bash theme={null}
curl -X POST https://back.leap.onclarity.com/api/webhooks/edit \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "webhook_id": 14, "status": "disabled" }'
```

## Delivery

| | |
| - | - |
| Method | `POST` |
| Headers | `Content-Type: application/json` |
| Body | The event payload: a JSON object whose `type` is the event key |
| Timeout | 30 seconds per attempt |
| Success | Any `2xx` response |
| Retries | Up to 3 attempts in total, one right after the other, on a network error, a timeout, or a non-`2xx` response. After the third failure the event is dropped. |
| Order | Not guaranteed. Events are delivered asynchronously. |

Each event is sent to every active webhook subscribed to it.

> [!WARNING]
>
> Requests are not signed and carry no secret or authentication header. Treat the payload as a notification: use a URL that is hard to guess, accept only `POST`, and when an action matters, read the current state back from the API (for example the ticket details) instead of trusting the body alone.

Design your handler to tolerate duplicates. A request that timed out on your side may still be retried, so the same event can arrive more than once. The payload carries no event id; deduplicate on the fields that identify the change, such as `ticket_id` with `type`.

## Event catalog

| Key | Sent when |
| - | - |
| `ticket_created` | A new ticket is opened. |
| `message` | A message is added to a ticket, by the customer, a human agent, or the AI agent. |
| `private_note` | An agent adds a private note. |
| `system_notification` | A system line is added to a ticket. |
| `status_change` | A ticket's status changes. |
| `ticket_closed` | A ticket moves to a closed status. Includes the ticket details and the full transcript. |
| `assignee_change` | A ticket's agent changes. |
| `team_change` | A ticket's team changes. |
| `priority_change` | A ticket's priority changes. |
| `category_change` | A ticket's category changes. |
| `ticket_tags_changed` | A ticket's tags change. |
| `snoozed` | A ticket is snoozed. |
| `snooze_end` | A ticket's snooze ends. |
| `survey_sent` | The customer rates a closed conversation. |
| `embedded_form_submitted` | The customer submits an embedded form. |
| `wa_template_message` | A WhatsApp template is sent on a ticket. |
| `prompt` | A customer message in a chat widget that runs in copilot mode. |
| `recommendation` | The AI's answer to a `prompt`. |
| `kb_article_created` | A knowledge base article is created. |
| `incident_detected`, `incident_updated`, `incident_resolved` | An incident is detected, changed, or resolved. Listed only when your organization has incident management. |

> [!NOTE]
>
> The catalog can also list `voice_call_ended` and `voice_call_warning`. They trigger workflows and are not delivered to webhook URLs. Use the Voice API's [call-ended webhook](/voice/call-ended-webhook.md) for calls.

## Payloads

Ids in payloads are the same ids the API uses: `ticket_id` works with the ticket endpoints, `agent_id` with `GET /api/agent/list`, status, priority, and category ids with their [lookup endpoints](/support/customers-and-tickets.md#look-up-ids-first).

### Messages

> **message**
>
> ```json theme={null}
> {
>   "type": "message",
>   "text": "Hi, my order has not arrived yet.",
>   "ticket_id": 12345,
>   "customer_id": 67890,
>   "agent_id": null,
>   "files": []
> }
> ```
>
> `agent_id` is `null` when the customer wrote the message. For a reply it is the id of the agent who sent it; check `kind` in `GET /api/agent/list` to tell the AI agent (`ai`) from a human (`human`). `files` holds attachment ids.

> **private\_note**
>
> ```json theme={null}
> {
>   "type": "private_note",
>   "text": "Customer called twice about this order.",
>   "agent_id": 311,
>   "mentioned_agent_ids": [207],
>   "files": []
> }
> ```
>
> This payload has no `ticket_id`.

> **system\_notification**
>
> ```json theme={null}
> {
>   "type": "system_notification",
>   "ticket_id": 12345,
>   "text": "Customer verified their email"
> }
> ```

> **wa\_template\_message**
>
> ```json theme={null}
> {
>   "type": "wa_template_message",
>   "ticket_id": 12402,
>   "template_id": "",
>   "variables": { "1": "Omar", "2": "4471" },
>   "types": { "twilio_text": { "body": "Hi Omar, your order 4471 is ready for pickup." } },
>   "customer_id": 70145,
>   "agent_id": 311,
>   "wa_channel_id": 3
> }
> ```
>
> `template_id` is currently always an empty string.

> **prompt and recommendation**
>
> ```json theme={null}
> {
>   "type": "prompt",
>   "text": "How do I change my delivery address?",
>   "ticket_id": 12345,
>   "orig_ticket_id": 12345,
>   "agent_id": 0,
>   "files": []
> }
> ```
>
> ```json theme={null}
> {
>   "type": "recommendation",
>   "text": "Open Orders, choose the order, and select Change address.",
>   "trigger_internal_id": "0199f3a2-6c1e-7d4b-9a10-2f5c8e1b7a44",
>   "ticket_id": 12345,
>   "orig_ticket_id": 12345,
>   "state": "done",
>   "reaction_state": null,
>   "suggested_actions": [],
>   "related_articles": [
>     { "id": "482913570216", "collection_id": "kb_42", "title": "Change a delivery address" }
>   ]
> }
> ```
>
> `trigger_internal_id` is the `internal_id` of the prompt being answered.

### Ticket lifecycle

> **ticket\_created**
>
> ```json theme={null}
> {
>   "type": "ticket_created",
>   "ticket_id": 12345,
>   "conversation_id": 5521,
>   "customer": {
>     "id": 67890,
>     "first_name": "Lena",
>     "last_name": "Park",
>     "avatar_url": null,
>     "organization_id": 42,
>     "attributes": [
>       { "key": "personal_email", "value": "lena.park@example.com" }
>     ],
>     "tags": [],
>     "custom_attributes": {},
>     "gender": null,
>     "gender_source": null,
>     "merged_into_id": null,
>     "has_open_suggestion": false,
>     "channels": []
>   },
>   "channel": "whatsapp"
> }
> ```
>
> `customer` has the shape of the [customer object](/support/customers-and-tickets.md#the-customer-object). `channel` is `web`, `android_sdk`, `ios_sdk`, `whatsapp`, `email`, `voice`, `x`, `instagram`, `linkedin`, or `social`.

> **status\_change**
>
> ```json theme={null}
> {
>   "type": "status_change",
>   "ticket_id": 12345,
>   "old_status_id": 1,
>   "new_status_id": 4,
>   "new_status_state": "closed"
> }
> ```

> **ticket\_closed**
>
> ```json theme={null}
> {
>   "type": "ticket_closed",
>   "ticket_id": 12345,
>   "conversation_id": 5521,
>   "channel": "web",
>   "ticket": {
>     "id": 12345,
>     "thread_root_id": null,
>     "status": { "id": 4, "name": "Resolved", "state": "closed" },
>     "priority": { "id": 2, "name": "Normal" },
>     "category": { "id": 18, "label": "Delivery" },
>     "team": { "id": 7, "name": "Logistics" },
>     "assignee": { "id": 311, "first_name": "Sam", "last_name": "Reyes", "email": "sam@example.com" },
>     "tags": [{ "id": 5, "name": "VIP" }],
>     "custom_attributes": { "order_number": "4471" },
>     "created_at": "2026-10-22T09:00:12.000000Z",
>     "closed_at": "2026-10-22T09:41:55.000000Z"
>   },
>   "customer": {
>     "id": 67890,
>     "first_name": "Lena",
>     "last_name": "Park",
>     "avatar_url": null,
>     "organization_id": 42,
>     "attributes": [],
>     "tags": [],
>     "custom_attributes": {},
>     "gender": null,
>     "gender_source": null,
>     "merged_into_id": null,
>     "has_open_suggestion": false,
>     "channels": []
>   },
>   "transcript": [
>     {
>       "id": 991201,
>       "created_at": "2026-10-22T09:00:12.000000Z",
>       "sender_type": "customer",
>       "agent_id": null,
>       "is_private_note": false,
>       "text": "Hi, my order has not arrived yet.",
>       "files": []
>     },
>     {
>       "id": 991214,
>       "created_at": "2026-10-22T09:00:20.000000Z",
>       "sender_type": "ai",
>       "agent_id": null,
>       "is_private_note": false,
>       "text": "Sorry about that. Let me check order 4471 for you.",
>       "files": []
>     }
>   ]
> }
> ```
>
> `transcript` holds the ticket's messages and private notes, oldest first; status and assignment events are left out. `sender_type` is `customer`, `human`, or `ai`. `ticket.custom_attributes` includes every attribute the ticket holds. `thread_root_id` is the first ticket of the chat when this ticket continues an earlier one.
>
> If the ticket could no longer be read when the event was sent, the payload carries only `type` and `ticket_id`.

> **assignee\_change and team\_change**
>
> ```json theme={null}
> {
>   "type": "assignee_change",
>   "ticket_id": 12345,
>   "old_assignee_id": null,
>   "new_assignee_id": 311,
>   "initiator_agent_id": null
> }
> ```
>
> ```json theme={null}
> {
>   "type": "team_change",
>   "ticket_id": 12345,
>   "old_team_id": null,
>   "new_team_id": 7
> }
> ```

> **priority\_change and category\_change**
>
> ```json theme={null}
> {
>   "type": "priority_change",
>   "ticket_id": 12345,
>   "old_priority_id": 2,
>   "new_priority_id": 1
> }
> ```
>
> ```json theme={null}
> {
>   "type": "category_change",
>   "ticket_id": 12345,
>   "old_category_id": null,
>   "new_category_id": 18
> }
> ```

> **ticket\_tags\_changed**
>
> ```json theme={null}
> {
>   "type": "ticket_tags_changed",
>   "ticket_id": 12345,
>   "old_tag_ids": [5],
>   "new_tag_ids": [5, 9]
> }
> ```

> **snoozed and snooze\_end**
>
> ```json theme={null}
> {
>   "type": "snoozed",
>   "ticket_id": 12345,
>   "until_dt": "2026-10-23T09:00:00+00:00"
> }
> ```
>
> ```json theme={null}
> {
>   "type": "snooze_end",
>   "ticket_id": 12345
> }
> ```

### Customer feedback and forms

> **survey\_sent**
>
> ```json theme={null}
> {
>   "type": "survey_sent",
>   "ticket_id": 12345,
>   "rating": 5,
>   "comment": "Quick and helpful."
> }
> ```

> **embedded\_form\_submitted**
>
> ```json theme={null}
> {
>   "type": "embedded_form_submitted",
>   "ticket_id": 12345,
>   "form_id": 3,
>   "submitted_form_id": 1207,
>   "data": { "order_number": "4471", "reason": "Damaged item" }
> }
> ```

### Knowledge base and incidents

> **kb\_article\_created**
>
> ```json theme={null}
> {
>   "type": "kb_article_created",
>   "article_id": 482913570216,
>   "title": "How to return an item",
>   "collection": "Returns",
>   "status": "published",
>   "source": "manually",
>   "created_by": "ai-agent",
>   "created_at": "2026-10-22T10:02:11.483920+00:00"
> }
> ```
>
> Sent for articles created in Clarity, through the API, and from uploads (an upload sends it when the article is created, while its status is still `processing`).

> **incident\_detected, incident\_updated, incident\_resolved**
>
> ```json theme={null}
> {
>   "type": "incident_updated",
>   "action": "confirmed",
>   "incident_id": 77,
>   "title": "Card payments failing at checkout",
>   "summary": "Customers report declined cards since 09:10.",
>   "likely_cause": null,
>   "severity": 2,
>   "status": "active",
>   "origin": "detected",
>   "category_ids": [18],
>   "ticket_count": 23,
>   "banner_enabled": true,
>   "banner_text": "We are aware of a payment issue and are working on a fix.",
>   "resolution_message": null,
>   "external_url": null,
>   "started_at": "2026-10-22T09:10:00+00:00",
>   "resolved_at": null
> }
> ```
>
> `action` says what happened: `detected` for `incident_detected`; `resolved` or `auto_resolved` for `incident_resolved`; and for `incident_updated` one of `created`, `confirmed`, `dismissed`, `edited`, `monitoring`, `reactivated`, `reopened`, `banner`, `guidance`, `voice_notice`, or `deleted`. `status` is `suspected`, `active`, `monitoring`, `resolved`, or `dismissed`; `origin` is `manual` or `detected`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.