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

Prev

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 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

  • 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

// 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
// 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 returned by the Payment Sessions API (upon session creation) and the upgraded Subscriptions API.

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).

Reference links