# Receiving webhooks (/docs/webhooks)



Karma pushes events to an HTTPS endpoint you control. Subscribe once, then
handle events as they happen instead of polling for changes.

## The envelope [#the-envelope]

Every event arrives as a `POST` with the same outer shape. Only `data` differs
between event types — its schema is documented on each event's page.

```json
{
  "id": "9f1c…",
  "event": "order.created",
  "resource": "order",
  "resourceId": "3b7e…",
  "timestamp": "2026-08-02T09:41:17.000Z",
  "apiVersion": "v1",
  "data": { }
}
```

| Field                     | Description                                                                       |
| ------------------------- | --------------------------------------------------------------------------------- |
| `id`                      | Unique id for this delivery group. Stable across retries — use it to deduplicate. |
| `event`                   | Event name, e.g. `order.created`.                                                 |
| `resource` / `resourceId` | The entity the event is about.                                                    |
| `timestamp`               | When the event occurred, ISO 8601 UTC.                                            |
| `apiVersion`              | Envelope version. Currently always `v1`.                                          |
| `data`                    | Event-specific payload.                                                           |

## Custom fields and metadata [#custom-fields-and-metadata]

HTTP webhooks include the object's API-readable custom fields in two forms:

* `customFields`: values with their field ID, stable key, label, group, type, display value, version and status.
* `metadata.customFields`: the same values keyed by the field's stable key. Other existing metadata is preserved.

For inventory, booking and order events these properties live directly under
`data`. Tab events that already contain a `tab` reference put them under
`data.tab`; other tab events put them under `data`. Existing `location`, `tab`,
`item`, `purchase`, `table` and `guest` reference blocks are enriched too,
including `items[].item`. A booking with a `purchaseId` also gets a `purchase`
reference. A staff `user` reference is **not** a guest profile.

Only fields enabled for API reading are included, respecting inherited
restrictions and the subscription's API-key permissions and location access.
Company-owned subscriptions without an API key receive API-readable fields
for their matched location. Unset fields are omitted; explicitly cleared
values remain `null`. Deleted or inaccessible records have empty field
projections. Decimal and money values retain the API's exact representation.

Values are captured when outbound dispatch is prepared, which can be later
than the event timestamp. They are **not sale-time snapshots**. Once saved for
a delivery, the values stay unchanged across retries, even if the fields are
edited afterwards. The signature covers the entire payload, including fields.
These subscriber-specific values are not added to browser realtime broadcasts.

This enrichment does not introduce new event types or emit an event merely
because a custom field was edited. In particular, there is currently no
standalone public purchase webhook; purchase references are enriched within
existing events.

## Verifying deliveries [#verifying-deliveries]

Each request carries an `X-Webhook-Signature` header: an HMAC-SHA256 of the raw
request body, keyed with your subscription's signing secret.

<Callout type="warn" title="Verify before you trust">
  Compute the HMAC over the **raw** body bytes, before any JSON parsing, and
  compare using a constant-time comparison. Reject any request whose signature
  does not match.
</Callout>

## Responding [#responding]

Respond `2xx` within **30 seconds** to acknowledge delivery. Anything else —
including a timeout — is treated as a failure and retried.

Acknowledge first, process afterwards: do the minimum work needed to persist
the event, return `2xx`, and handle the rest asynchronously. Slow handlers are
the most common cause of duplicate deliveries.

## Retries, ordering and duplicates [#retries-ordering-and-duplicates]

* **Retries** — failed deliveries are retried with backoff. Your endpoint must
  be idempotent.
* **Duplicates** — at-least-once delivery. The same `id` may arrive more than
  once; deduplicate on it.
* **Ordering** — not guaranteed. Use `timestamp` to order events, and don't
  assume `order.updated` arrives after `order.created`.

## Subscribing [#subscribing]

Create and manage subscriptions through the Webhooks endpoints in the API
reference. A subscription is a URL, the set of events it should receive, and
the location scope it applies to.
