--- title: "Migrating to new subscription version" slug: "migrate-from-legacy-subscriptions-to-new-subscriptions" updated: 2026-08-13T12:23:32Z published: 2026-08-13T12:23:32Z canonical: "docs.xendit.co/migrate-from-legacy-subscriptions-to-new-subscriptions" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.xendit.co/llms.txt > Use this file to discover all available pages before exploring further. # Migrating to new subscription version This guide explains how to migrate existing Xendit legacy Subscriptions integrations `POST /recurring/plans` with `api-version: 2022-04-10`) to the new Subscriptions version `api-version: 2026-01-01`), which consolidates the subscription workflow with the Payment Sessions product. > Scoped to the recurring plan lifecycle `/recurring/plans`) and its Payment Session `session_type: SUBSCRIPTION`) entry point. If you're integrating a one-off Payment Link or Payment Request, see [Migrate from Payment Links/Invoice to Payment Session](/v1/docs/migrate-to-payment-session) instead. ## Why you should migrate - **Both Payment Link and Components, from one integration**. Legacy Subscriptions only supports the Payment Link (hosted redirect) flow for collecting a payment method. The new version supports both Payment Link and Components (embedded SDK), so you're not locked into a redirect if you want to keep the customer on your own checkout page. - **Full channel and region coverage**. Legacy Subscriptions is limited to legacy payment methods and specific regions, mirroring the same constraint that Legacy Payment Link had before Payment Sessions. The new version inherits the full channel/region catalog from Payment Sessions. - **One lifecycle model instead of two**. Legacy Subscriptions tracks its own lifecycle on the `payment_method` object separately from the UI/session experience — meaning you reconcile two different mental models for the same transaction. The new version applies the same "one object, one lifecycle" principle used in Payment Sessions and Payment v3/Payment Tokens v3 across both the payment interface and the underlying payment object. - **A cleaner initial-payment pattern.** Legacy Subscriptions only exposed `immediate_action_type` to trigger a payment at plan creation — an enum-driven flag bolted onto the plan. The new version recommends the `PAY_AND_SAVE` session flow (create the payment and generate the payment token as the initial payment, and use the payment token on subscription for the upcoming schedule) ## Overall changes Before touching any code, a few things your team should know going in: - **There are two migration paths, not one.** - If you need Xendit to collect and tokenize the payment token (new customer), you now go through Payment Sessions `POST /sessions` with `session_type: SUBSCRIPTION`) rather than `POST /recurring/plans` directly. - If you already hold a valid `payment_token_id` for the customer (returning customer, token created elsewhere), you continue calling `POST /recurring/plans` — just with the new `payment_tokens` parameter instead of the legacy `payment_methods` parameter. Route your engineering work accordingly; treating this as a single flat migration will cause the wrong team to pick up the wrong ticket. - You can reuse the same identifier from Payment Methods to Payment Tokens — just swap the prefix from `pm-` to `pt-` . Please find more details on [this page](/v1/docs/migrate-payment-api-v2-to-v3#42-payment-method-v2-→-payment-token-v3-—-full-field-mapping) - **The** `api-version` **header is mandatory and versioned.** Legacy Subscriptions runs on `2022-04-10`; the new version requires `2026-01-01`. - `recurring_action` **is removed, not renamed**. The new version streamlines the flow to default exclusively to PAYMENT behavior. Any code path that sets or reads `recurring_action` for a non-payment purpose needs to be removed, not remapped. - **Three legacy notification fields collapse into one**. `notification_config.recurring_created`, `notification_config.recurring_created`, and `notification_config.recurring_failed` (three separate keys in the legacy payload) become a single `notification_channels` . If your legacy config sends different channels per event today, confirm whether the new version supports that granularity before cutting over, or plan to standardize on one channel set for all notification events. - **Webhook payloads and event names both changed.** The new version runs on Xendit's principle over Payment API v3 and Payment Tokens v3 webhook infrastructure rather than the legacy `payment_method.*` events. See Webhook event mapping below. | Area | Legacy Subscription | New Subscription | | --- | --- | --- | | Payment Link and Components support | Payment Link only | Both Payment Link and Components | | Payment channel / region availability | Limited to legacy channels and specific regions | All channels and regions Xendit supports | | Lifecycle model | Separate lifecycles on `payment_method` object vs. UI/session experience | One integration principle across the payment interface and payment object | | Initial payment on plan creation | `immediate_action_type` (legacy parameter only) | Recommended: `PAY_AND_SAVE` Payment Session flow; backward-compatible via `immediate_payment` | ## Detailed changes ### How to migrate #### Update your integration flow - **Bump your API version header**. Change `api-version` from `2022-04-10` to `2026-01-01` to access the new features. - **Collecting a new payment method (hosted UI)**: instead of calling `POST /recurring/plans` directly, call `POST /sessions` with `session_type: "SUBSCRIPTION"`. The response returns a `payment_link_url` (mode `PAYMENT_LINK`) or `components_sdk_key` (mode `COMPONENTS`) depending on your integration mode, in place of the legacy response's `actions.url`. - **Reusing an existing payment token**: continue calling `POST /recurring/plans`, but replace the legacy `payment_methods` parameter with the new, unified `payment_tokens` parameter. **Use Case A — Creating a new payment token (customer hasn't linked a payment token yet)** Call `POST /sessions` directly with `session_type: "SUBSCRIPTION"` . This replaces calling `POST /recurring/plans` up front. Xendit collects and tokenizes the payment token via the hosted page (or Components), then creates the plan on your behalf once linking succeeds. **Use Case B — Reusing an existing payment token (customer already linked)** If you already hold a valid `payment_token_id` for the customer, skip Payment Sessions entirely and call `POST /recurring/plans` directly, replacing the legacy `payment_methods` parameter with payment_tokens. Learn more on the differences on our [subscription integration page](https://docs.xendit.co/docs/usage-based-subscriptions) **Create new payment token** ```json // Create new payment token // POST https://api.xendit.co/sessions {  "reference_id": "subscription_12345",  "session_type": "SUBSCRIPTION",  "mode": "PAYMENT_LINK",  "amount": 500,  "currency": "PHP",  "country": "PH",  "customer": {    "reference_id": "cust_Lorem_Ipsum",    "type": "INDIVIDUAL",    "email": "test@yourdomain.com",    "mobile_number": "+6212345678",    "individual_detail": {      "given_names": "Lorem",      "surname": "Ipsum"    }  },  "locale": "en",  "description": "Monthly subscription plan",  "subscription": {    "schedule": {      "interval": "MONTH",      "interval_count": 1,      "total_recurrence": 12,      "anchor_date": "2026-09-01",      "retry_interval": "DAY",      "retry_interval_count": 1,      "total_retry": 3,      "failed_attempt_notifications": [1, 2, 3]    },    "failed_cycle_action": "RESUME"  },  "success_return_url": "https://yourcompany.com/success",  "cancel_return_url": "https://yourcompany.com/cancel" } ``` **Reuse existing payment token** ```json // Reuse existing payment token // POST https://api.xendit.co/recurring/plans // header: api-version: 2026-01-01 {  "reference_id": "my-plan-01",  "customer_id": "cust-b98d6f63-d240-44ec-9bd5-aa42954c4f48",  "currency": "IDR",  "amount": 150000,  "schedule": {    "interval": "MONTH",    "interval_count": 1,    "total_recurrence": 12,    "anchor_date": "2026-09-01T00:00:00Z",    "retry_interval": "DAY",    "retry_interval_count": 1,    "total_retry": 3,    "failed_attempt_notifications": [1, 2, 3]  },  "payment_tokens": [    { "payment_token_id": "pt-f8429206-f3ea-49f0-abb4-eaa89064056e", "rank": 1 }  ],  "immediate_payment": true,  "failed_cycle_action": "RESUME",  "notification_channels": ["EMAIL"],  "description": "Monthly premium subscription" } ``` ### Field mapping on subscription object — what’s change | Legacy Subscription | New Subscription | Notes | | --- | --- | --- | | `payment_methods` | `payment_tokens` | **Renamed** — only present when charging a previously saved token. Prefix changed from `pm-` to `pt-` | | `actions.url` | *removed — replaced by* `payment_link_url` *or* `components_sdk_key` *on the session with type subscription* | Changes on the flow for action are moved from subscription endpoint to session endpoint | | `notification_config.recurring_created`, `notification_config.recurring_created`, `notification_config.recurring_failed` | `notification_channels` | Consolidated into a single array to configure notification channels for the plan. | | `immediate_action_type` | — | We recommend you to use `PAY_AND_SAVE` session flow (create the payment and generate the payment token as the initial payment, and use the payment token on subscription for the upcoming schedule) | #### Handle new error responses Ensure your server's error-handling logic is updated to catch and process the [new error structures](https://docs.xendit.co/apidocs/create-session) returned by the [**Payment Sessions** API](https://docs.xendit.co/apidocs/create-session) (upon session creation) and the upgraded [**Subscriptions** API](https://docs.xendit.co/apidocs/create-recurring-plan). #### Adjust your webhook setup The new version utilizes Xendit's upgraded **Payment API v3** webhook infrastructure. We highly recommend updating your webhook URL endpoints to cleanly capture and process updates across your subscription lifecycles. | Legacy Subscription | New Subscription | Notes | | --- | --- | --- | | **Plan Callback Event** | | `recurring.plan.activated` / `recurring.plan.inactivated` | `recurring.plan.activated` / `recurring.plan.inactivated` | Different payload structure (i.e. no `immediate_action_type` and different `actions.url` | | **Cycle Callback Event** | | `recurring.cycle.created` | `recurring.cycle.created` | Different payload structure | | `recurring.cycle.succeeded` | `recurring.cycle.succeeded` | | `recurring.cycle.failed` | `recurring.cycle.failed` | | **Payment Callback Event - follow the Payments API v3 principle** | | | `payment.capture` | Triggered when payment is succeeded, in concurrent with the `recurring.cycle.created` for the details of the payment object | | | `payment.failure` | Triggered when payment is failed, in concurrent with the `recurring.cycle.failed` for the details of the payment object | > **Rule of thumb**: treat `recurring.plan.*` and `recurring.cycle.*` as your **subscription lifecycle** source of truth, and treat `payment.*` / `payment_token.*` as your **transaction/instrument** source of truth — the same separation-of-concerns pattern used across the rest of Xendit's v3 migration ([Payment Sessions, Payment API v2→v3](/v1/docs/migrate-from-payment-api-v2-to-payment-api-v3)). ## Reference links - [Subscriptions Overview](https://docs.xendit.co/docs/subscriptions-overview) - [How subscriptions work](https://docs.xendit.co/docs/how-subscriptions-work) - [Create Subscription Plan — API reference](https://docs.xendit.co/apidocs/create-recurring-plan) - [Create a Session — API reference](https://docs.xendit.co/apidocs/create-session)