# Upgrading webhooks to payload version 2

Payload version 2 changes the shape of `data` in the webhooks you receive. It is optional and separate from the API version. It has no deadline: a subscriber on payload version 1 keeps receiving it after Core API v1 stops being served on 4 November 2026.

## What version 2 gives you

* **`data` is the resource at the moment of the event.** On version 1, `data` is read when the webhook is built, so it can include changes made after the event. Version 2 takes its snapshot together with the change, [with a rare exception](https://docs.odus.com/docs/guides/api-features/webhooks.md#when-version-2-cannot-capture-the-event).
* **A fixed shape you can generate types from.** Each resource has a fixed set of fields, published for every event in the [Webhooks section of the API reference](https://docs.odus.com/docs/api/core/v2/webhook-customer-created). The shape does not change when an API response changes.
* **Deletion events carry the deleted resource.** Version 2 normally sends the resource as it was when it was removed. Version 1 sends only its `id`.
* **An `.updated` event for every change.** On payments, customers, subscriptions, invoices, products and prices, `<resource>.updated` is sent whenever a version 2 field changes.
* **Smaller payloads.** Related objects are sent as their ID instead of as expanded objects.

[Payload versions](https://docs.odus.com/docs/guides/api-features/webhooks.md#payload-versions) compares the two versions in full.

## Move a subscriber to version 2

You cannot change the payload version of an existing subscriber. Run a second subscriber on version 2 next to the one you have, then deactivate the old one when the new one works.

1. **Make room for one more active subscriber.** You can have at most five active subscribers, and a new subscriber is always created active. If you already have five, deactivate one you can spare first. Don't deactivate the subscriber you are replacing yet.

2. **Prepare your endpoint for version 2.** Update your handler to read the [version 2 fields](#fields-by-resource). Until you finish the move, your endpoint receives both versions. If both subscribers use the same URL, read `profile` from the body to know which subscriber sent the request. Then verify the signature with that subscriber's shared secret. You can also give the new subscriber a different URL, for example by adding a query parameter.

3. **Create the version 2 subscriber.** Subscribe it to the same events as the old one, and set `payloadVersion` to `2` in [`POST /v2/webhook-subscribers`](https://docs.odus.com/docs/api/core/v2/webhook-subscriber-controller-create), or choose **Version 2** when you add it in the [dashboard](https://docs.odus.com/docs/guides/dashboard/webhook-subscriber.md). Save the shared secret, because it is shown only once.

   Creating a version 2 subscriber

   ```
   {

     "recipientUrl": "https://example.com/webhooks",

     "subscribedTo": ["payment.succeeded", "payment.captured"],

     "payloadVersion": 2

   }
   ```

4. **Handle both versions while they overlap.** Each event is delivered to each subscriber separately, with the same `eventId`. Record the `eventId`s you have processed and skip one you have already handled, so that you act on each event once.

5. **Confirm the new subscriber works.** Check that your endpoint accepts and processes the version 2 deliveries for every event type you subscribe to.

6. **Let the old subscriber finish retrying.** Deactivating it pauses the retries of its failed deliveries. List them with [`GET /v2/webhook-deliveries`](https://docs.odus.com/docs/api/core/v2/webhook-controller-find-all-deliveries-paginated):

   ```
   GET /v2/webhook-deliveries?subscriber[eq]=<old_subscriber_id>&success[eq]=false
   ```

   Page through all of the results with the cursor each time you check, starting from the first page, because deliveries that have stopped being retried stay in the list. Repeat the check until every delivery it returns has stopped being retried: `attempts` has reached 10 or `abandonedAt` is set. A delivery that succeeds on a retry drops out of the list. Retries normally finish within about 3 days, but a delivery can stay pending for longer.

7. **Deactivate the old subscriber, at least an hour after you created the new one.** A new subscriber receives only events that happen after you create it. An earlier event that has not been sent to the old subscriber when you deactivate it is not sent to either, and the waiting time leaves room for those events to be sent. Then send `{ "active": false }` to [`POST /v2/webhook-subscribers/{id}`](https://docs.odus.com/docs/api/core/v2/webhook-subscriber-controller-update), or select **Archive** for it in the dashboard. There is no delete operation. A deactivated subscriber stays in your list.

## Fields by resource

This section explains the fields of `data` on payload version 2. Each resource has a fixed set of fields, and every event about that resource uses the same set. The [Webhooks section of the API reference](https://docs.odus.com/docs/api/core/v2/webhook-customer-created) lists the fields of each event, with their types.

### How to read the fields

* Every top-level field listed in the API reference is present. When the resource has no value for a field, the field is `null`. The one exception is the [rare case](https://docs.odus.com/docs/guides/api-features/webhooks.md#when-version-2-cannot-capture-the-event) where `data` contains only `id`.
* Dates are strings in ISO 8601 format.
* A related object is sent as its ID only, never as the expanded object. To get the full object, retrieve it from the API with that ID. A price's `billingSchedule` and a subscription's `setupLineItem` have no retrieve endpoint of their own.
* `metadata` is the resource's metadata after the change that caused the event.
* A field the API reference does not list is not sent. If you need it, retrieve the resource from the API. After a deletion event the resource can no longer be retrieved, so keep any field you need for handling a deletion on your side beforehand.

Version 2 sends fewer fields than version 1, but it still contains customer personal data, such as email addresses, phone numbers, dates of birth and addresses. Protect webhook payloads in the same way for both versions.

### Payment

Sent with every `payment.*` event.

Fields: [`payment.created`](https://docs.odus.com/docs/api/core/v2/webhook-payment-created) in the API reference.

`captureMethod` is the payment's capture method. In the API response, it is inside `paymentMethodOptions`. The payment response fields `displayStatus`, `cart` and `paymentMethodOptions`, and the `is…` flags such as `isCaptured`, are not sent.

### Transaction

Sent with every `transaction.*` event.

Fields: [`transaction.capture_failed`](https://docs.odus.com/docs/api/core/v2/webhook-transaction-capture-failed) in the API reference.

### Customer

Sent with every `customer.*` event.

Fields: [`customer.created`](https://docs.odus.com/docs/api/core/v2/webhook-customer-created) in the API reference.

`dateOfBirth` is a date only, in `YYYY-MM-DD` format. The customer's payment methods and subscriptions are not sent.

### Blacklist entry

Sent with every `blacklist.*` event.

Fields: [`blacklist.added`](https://docs.odus.com/docs/api/core/v2/webhook-blacklist-added) in the API reference.

`blacklist.removed` normally carries the entry as it was when it was removed.

### Subscription

Sent with every `subscription.*` event.

Fields: [`subscription.active`](https://docs.odus.com/docs/api/core/v2/webhook-subscription-active) in the API reference.

### Invoice

Sent with every `invoice.*` event.

Fields: [`invoice.created`](https://docs.odus.com/docs/api/core/v2/webhook-invoice-created) in the API reference.

### Product

Sent with every `product.*` event.

Fields: [`product.created`](https://docs.odus.com/docs/api/core/v2/webhook-product-created) in the API reference.

### Price

Sent with every `price.*` event.

Fields: [`price.created`](https://docs.odus.com/docs/api/core/v2/webhook-price-created) in the API reference.

`price.deleted` normally carries the price as it was when it was deleted.
