# Receive events with webhooks

> Events, request format, signature check and retries. Available on Team and Business.

A webhook sends an HTTPS `POST` to your server when something happens in your workspace, for example when a conversation starts or a contact changes. Set them up under **Settings → Workspace → API & webhooks → Add webhook**.

Webhooks are part of the Team plan (5 endpoints) and the Business plan (25 endpoints). Only owners and admins can manage them.

## Events

| Event | Sent when |
| --- | --- |
| conversation.created | A conversation starts |
| conversation.updated | Status, assignment or labels of a conversation change |
| conversation.closed | A conversation is resolved |
| message.created | A new message arrives or is sent. Internal notes are not included. |
| contact.created | A contact is saved |
| contact.updated | A contact changes |
| rating.created | A visitor rates a conversation |

**Send test** sends a `webhook.test` event to check your endpoint. You can't subscribe to it.

## The request

Every request is a `POST` with a JSON body:

```json
{
  "id": "cm3x9…",
  "event": "conversation.created",
  "createdAt": "2026-11-06T09:12:44.120Z",
  "workspaceId": "cm1a2…",
  "data": { }
}
```

`data` holds the object the event is about. To see its exact fields, open the webhook, go to **Deliveries** and look at **Sent** for any delivery.

Headers:

| Header | Value |
| --- | --- |
| X-Asktrix-Event | The event name |
| X-Asktrix-Delivery | The delivery ID. It's the same as id in the body and doesn't change between retries. |
| X-Asktrix-Signature | t=<unix timestamp>,v1=<signature> |
| User-Agent | asktrix-webhooks/1 |

Answer with any `2xx` status within 10 seconds. Redirects are not followed and count as a failure.

## Check the signature

Each webhook has its own **Signing secret** (it starts with `whsec_`). The signature is an HMAC-SHA256, in hex, of the timestamp, a dot, and the raw request body.

Check it before you trust the request:

1.  Read `t` and `v1` from the `X-Asktrix-Signature` header.
2.  Reject the request if `t` is more than 5 minutes away from your server time.
3.  Compute HMAC-SHA256 of `<t>.<raw body>` with your secret.
4.  Compare it with `v1` in constant time.

Use the raw body exactly as received. If your framework parses the JSON first and you serialize it again, the signature won't match.

Node.js example:

```js
import { createHmac, timingSafeEqual } from "node:crypto";

export function isValidAsktrixRequest(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = String(parts.v1 ?? "");
  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
```

**Rotate** creates a new secret. The old one stops working immediately, so update your server right after rotating.

## Retries

If a delivery fails (timeout, connection error or a status outside `2xx`), we try again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. That's six attempts in total. A retry has the same `id`, so you can skip duplicates.

If your endpoint keeps failing (20 failed attempts in a row, and a delivery has used up all six attempts), the webhook is switched off and the owners and admins of the workspace get a notification. In practice that means hours of failures, not a short outage. Fix your endpoint, then switch the webhook back on with **Webhook active**.

## Delivery log

**Deliveries** shows the last 30 days per webhook: status, attempts, duration, what we sent and what your server answered. Filter by **Failed** and use **Resend** to deliver a single event again.

## Limits

-   The endpoint URL must use `https://` and be reachable from the internet. Private and local addresses are blocked.
-   Webhooks only fire while your workspace is on a plan that includes them. After a downgrade, your endpoints are kept but receive nothing.

For every event payload and verification code in Node.js, Python and PHP, see the [webhook developer docs](/docs/webhooks).

---
Source: https://localhost:3100/help/en/webhooks
