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

# Versioning

> Understand PayNext webhook API versions, the granular v2 event taxonomy, the api_version field, and idempotency.

PayNext webhooks are versioned. Version 2 introduces a **granular event taxonomy**: one event type maps to one concrete lifecycle transition, so you know exactly what happened without diffing the payload.

New endpoints default to **v2**. v1 and v2 run in parallel—for each state change, PayNext emits both the legacy v1 event and the corresponding v2 event, and each endpoint receives only the version it subscribed to.

## Versions at a glance

|                         | v1 (legacy)                                                              | v2                                                                                    |
| :---------------------- | :----------------------------------------------------------------------- | :------------------------------------------------------------------------------------ |
| **Payment events**      | `payment.created`, `payment.updated`                                     | `payment_v2.created`, `payment_v2.settled`, `payment_v2.declined`, …                  |
| **Subscription events** | `subscription.created`, `subscription.updated`, `subscription.cancelled` | `subscription_v2.activated`, `subscription_v2.renewed`, `subscription_v2.past_due`, … |
| **Refund events**       | `refund.created`, `refund.updated`                                       | `payment_v2.refund.created`, `payment_v2.refund.settled`, …                           |
| **`api_version`**       | absent                                                                   | `"2.0.0"`                                                                             |
| **Granularity**         | one event covers many transitions                                        | one event = one transition                                                            |
| **Status**              | deprecated                                                               | recommended                                                                           |

See [Event Types](/webhooks/introduction/event-types) for the full catalog and the v1 → v2 mapping.

## The `api_version` field

Every v2 event carries an `api_version` field in the envelope so your consumer knows which schema it's parsing:

```json theme={"system"}
{
  "id": "evt_6a608b3e-26c2-4b1c-ba4e-f199c30b584c",
  "object": "event",
  "type": "payment_v2.settled",
  "api_version": "2.0.0",
  "created_at": "2025-06-18T10:15:30Z",
  "data": {
    "object": { "payment": { "...": "..." } }
  }
}
```

The `data.object` payload shape is otherwise identical to v1. `api_version` is the only additive field.

<Note>
  v1 events do not include `api_version`. Branch on the presence of the field, or on the `_v2` prefix in `type`, to route events to the correct handler.
</Note>

## How versions are grouped

In the Dashboard **App Portal**, event types are grouped by the first segment of their name (the part before the first dot). This keeps each version in its own group:

* **`payment_v2`** — payments and refunds. A refund rides on the payment object, so refund events are nested here as `payment_v2.refund.*`.
* **`subscription_v2`** — subscription lifecycle events.

The legacy `payment`, `subscription`, and `refund` groups remain available for existing v1 endpoints. An endpoint subscribed to v1 receives only legacy names; an endpoint subscribed to v2 receives only `_v2` names.

## Sunset

v1 and v2 run in parallel for a 90-day overlap, starting **August 7, 2026**. On **November 5, 2026**, the v1 event types (`payment.*`, `subscription.*`, `refund.*`) are archived: PayNext stops emitting them and existing endpoints stop receiving them.

<Warning>
  Migrate your endpoints to v2 before **November 5, 2026**. After that date, v1 events are no longer delivered.
</Warning>

## Idempotency

Each transition emits exactly once, and `event.id` is **stable across delivery retries**. When PayNext retries a delivery, it reuses the same `event.id`, so your consumer can safely de-duplicate.

Store processed `event.id` values and skip any you've already handled:

```javascript theme={"system"}
async function handleWebhook(event) {
  if (await alreadyProcessed(event.id)) {
    return; // duplicate delivery—ignore
  }
  await process(event);
  await markProcessed(event.id);
}
```

<Note>
  For refund events, `event.id` is derived from the payment, the refund status, and the refund's creation time—retries of the same refund transition carry the same id, while a different refund on the same payment gets its own.
</Note>
