# Webhooks to your own systems

> Send orders, stock and product changes to your own systems as signed HTTPS posts, check the signature, and how retries, the delivery log and automatic turning off work.

Source: https://docs.bifrostorbit.com/commerce/developers/outgoing-webhooks/

A webhook sends the events you choose to an address of yours, such as a warehouse system or an ERP, as they happen. Webhooks are in the Growth and Enterprise plans, with the management API. Admins add them under **Webhooks**.

## Add a webhook

1. Open **Webhooks** and select **New webhook**.
2. Enter the address, starting with `https://`, such as `https://erp.northwilds.com/hooks/commerce`, and what it's for.
3. Choose the events, such as **Order placed** and **Order paid**.
4. Select **Save**, and copy the signing secret. It starts with `whsec_` and is shown only once. The key button makes a new one; the old one stops working at once.

**Send a test** posts a `commerce.webhook.test` event straight away and shows the answer. Each shop can have 10 webhooks, and only gets events from its own shop.

## What arrives

A `POST` with a JSON body:

```json
{
  "id": "1f6c6a52-4f7e-4a63-9c39-5d8b2b6c0c11",
  "type": "commerce.order.paid",
  "createdAt": "2026-10-30T09:14:02.118Z",
  "organisationId": "6650c0ffee0000000000abcd",
  "subject": { "resourceType": "order", "id": "6650c0ffee0000000000f00d" },
  "data": { "orderId": "6650c0ffee0000000000f00d", "reference": "NW1042", "status": "open", "paymentStatus": "paid", "fulfilment": "unfulfilled", "total": 46700, "currency": "EUR", "lines": 3, "source": "checkout" }
}
```

`data` is the same as the event's data in [Events](https://docs.bifrostorbit.com/commerce/developers/webhooks-and-events/). Fetch anything more, such as the order's lines, from the API.

| Header | What it holds |
| --- | --- |
| `Orbit-Signature` | `t=<unix seconds>,v1=<signature>` |
| `Orbit-Webhook-Id` | The delivery's id, the same on every retry. Use it to spot duplicates. |
| `Orbit-Event` | The event type. |
| `User-Agent` | `CommerceOrbit-Webhooks/1` |

Answer with any 2xx status within 10 seconds. Do the work afterwards, not before you answer. Redirects aren't followed.

## Check the signature

The signature is the hex HMAC SHA-256 of the time, a full stop and the raw body, with your signing secret as the key. Compare it in constant time, and refuse a time more than five minutes old.

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

export function verified(header: string, body: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=") as [string, string]));
  const time = Number(parts.t);
  if (!time || Math.abs(Date.now() / 1000 - time) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${time}.${body}`).digest("hex");
  return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
```

Use the body exactly as it arrived, before parsing it.

## Retries

A delivery that doesn't get a 2xx answer is tried again after 1 minute, then 5 and 30 minutes, then 2, 6, 12 and 24 hours, 8 tries in all. **Recent deliveries** under each webhook shows every delivery with its answer and time, and **Send again** for one that failed. Deliveries are kept for 30 days.

A webhook that has failed at least 10 times over 3 days is turned off, and Admins get a notification in Orbit. Fix your system, then turn it on again. Events while it was off aren't sent later.

## Addresses

Webhooks only go to `https` addresses on the internet. Addresses that lead to a private network, such as `10.0.0.5` or a name that resolves to one, are refused.

## Plans

On a plan without the management API, webhooks stop and no new events are queued, and nothing is lost from your settings. See [Plans](https://docs.bifrostorbit.com/commerce/everyone/plans/).
