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

# Migrate webhooks to v2

> Move an order callback from v1 deliveries to v2 events: what changes, and how to switch.

A callback receives the deliveries of the API version it was registered through. Callbacks registered with `POST /api/v1/orders` keep receiving v1 deliveries; callbacks registered with `POST /api/v2/orders` receive v2 events. Nothing changes for a callback until you register it through v2.

## What changes

| | v1 | v2 |
| - | - | - |
| Body | The order, in the v1 format (`"object": "order"`) | An event naming the order (`"object": "event"`) |
| Order state | In the body | Fetched from `data.url` |
| `X-Signature` | `sha256=<hex>`, over the raw body | `t=<unix seconds>,sha256=<hex>`, over `<t>.<raw body>` |

Everything else is the same in both versions: the `callback` object you send, the authentication options, the request headers, the outbound IP addresses, and the [retry policy](/webhooks#delivery-and-retries).

## Fetch the order for its state

A v2 event names the order and carries no status:

```json theme={null}
{
  "object": "event",
  "id": "4821",
  "type": "order.updated",
  "createdAt": "2026-10-02T10:00:00.000Z",
  "data": {
    "object": "order",
    "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
    "code": "CPmJ6QbYMo",
    "url": "/api/v2/orders/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
  }
}
```

`data.url` is a path relative to the API origin. Fetch it with an API token that has the `order.read` scope:

```bash theme={null}
curl https://api.origin.one/api/v2/orders/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 \
  --header "Authorization: Bearer $ORIGIN_ONE_API_TOKEN"
```

The response is the order in the v2 format, not the v1 body — see [Get an order](/api-reference/orders/get) for its fields and [Order lifecycle](/order-lifecycle) for its statuses. Act on the state you fetch, not on the number or order of events: see [Duplicates and ordering](/webhooks#duplicates-and-ordering).

## Verify the v2 signature

The v2 `X-Signature` header adds the send time: `t=<unix seconds>,sha256=<hex>`, where `sha256` is the HMAC-SHA256 of `t`, a `.`, and the raw body, keyed with the same `callback.auth.secretKey`. Reject a delivery whose `t` is more than 5 minutes old. [Verify the signature](/webhooks#verify-the-signature) has the steps and sample code.

A v1 signature stays `sha256=<hex>`, the HMAC-SHA256 of the raw body alone. Both versions sign the exact bytes sent, so verify against the body as received.

## Switch a callback to v2

1. **Accept both versions.** Deliveries queued before the switch keep their v1 body and signature, so your endpoint receives both for a while. Choose the verification by the `X-Signature` format (a value starting with `t=` is v2), then branch on the body's `object`: `"event"` is v2, `"order"` is v1.
2. **Register through v2.** For a new order, send the `callback` with `POST /api/v2/orders`. An existing order's callback moves to v2 when `POST /api/v2/orders` returns that order (`200 OK` with a `matched` block) and the request's `callback.url` is the one already registered. Send the whole `callback`, `auth` and `headers` included: any the request omits are removed. See [Register a callback again](/webhooks#register-a-callback-again).
3. **Keep accepting v1 until queued deliveries drain.** An event keeps the version it was created with on every retry, so v1 deliveries can arrive for up to about an hour after the switch.

Registering a different `url` through v2 adds a second registration instead of moving the first: the old URL keeps receiving v1 deliveries. To stop v1 deliveries to a URL, register that same URL through v2.


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