***

title: Subscription change requests
subtitle: Multi-step workflow for previewing and applying subscription changes with explicit payment control, conflict resolution, and idempotent retry support.
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.paymentkit.com/guides/billing/subscriptions/llms.txt. For full documentation content, see https://docs.paymentkit.com/guides/billing/subscriptions/llms-full.txt.

The change request API provides a multi-step workflow for making complex subscription changes with preview, payment control, and retry safety. Use this when you need to:

* **Preview changes before committing** — show customers the exact proration amounts before applying
* **Require payment before changes** — guarantee payment succeeds before modifying the subscription (Charge First pattern)
* **Handle retries safely** — built-in idempotency prevents double-charging on retry
* **Batch multiple changes** — combine item updates, coupon changes, and balance adjustments in one atomic operation

<Tip>
  For simpler immediate changes within the same billing interval, consider the [update subscription items](/billing/subscription-update#update-subscription-items) endpoint instead.
</Tip>

# How it works

The change request workflow has four steps:

```mermaid
stateDiagram-v2
    [*] --> DRAFT: 1. Create change request
    DRAFT --> DRAFT: 2. Add changes
    DRAFT --> READY: 3. Preview
    READY --> APPLIED: 4. Apply (with payment)
    READY --> CANCELLED: Cancel
    DRAFT --> CANCELLED: Cancel
    APPLIED --> [*]
    CANCELLED --> [*]
```

1. **Create** — Initialize a draft change request for a subscription
2. **Add changes** — Specify item changes (add/update/delete), coupon changes, and balance adjustments
3. **Preview** — Compute execution plan, detect conflicts, calculate proration (transitions to READY)
4. **Apply** — Collect payment (if needed) then execute changes (transitions to APPLIED)

Each change request is an independent entity with its own ID and status, allowing you to create, preview, and apply changes over multiple API calls.

# Create a change request

Create a new draft change request for a subscription. Only one active (DRAFT or READY) change request can exist per subscription.

<Tabs>
  <Tab title="API">
    ```bash
    curl -X POST https://api.paymentkit.com/api/{account_id}/change-requests \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "subscription_id": "sub_abc123",
      "reason": "Upgrade to annual plan",
      "expires_in_hours": 24
    }'
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python
    change_request = client.change_requests.create(
        account_id="acc_abc123",
        subscription_id="sub_abc123",
        reason="Upgrade to annual plan",
        expires_in_hours=24
    )
    ```
  </Tab>
</Tabs>

## Request fields

| Field              | Type    | Required | Description                                                                                                                                 |
| ------------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscription_id`  | string  | Yes      | External ID of the subscription to modify                                                                                                   |
| `reason`           | string  | No       | Optional reason for the change (stored as metadata)                                                                                         |
| `expires_in_hours` | integer | No       | Hours until the draft expires (default: 24). The change request automatically transitions to EXPIRED status after this time if not applied. |

## Response

```json
{
  "id": "chg_live_a1b2c3d4",
  "subscription_id": "sub_abc123",
  "status": "draft",
  "reason": "Upgrade to annual plan",
  "created_at": "2026-04-14T10:00:00Z",
  "expires_at": "2026-04-15T10:00:00Z",
  "item_changes": [],
  "coupon_changes": [],
  "balance_changes": []
}
```

<Warning>
  If the subscription already has an active change request, this endpoint returns a 409 Conflict error. Cancel the existing request first or use its ID to continue the workflow.
</Warning>

# Add changes

Add item changes, coupon changes, or balance adjustments to a draft change request. You can call this endpoint multiple times to build up a complex change plan.

<Tabs>
  <Tab title="API">
    ```bash
    curl -X POST https://api.paymentkit.com/api/{account_id}/change-requests/{change_request_id}/changes \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "item_changes": [
        {
          "action": "update",
          "item_id": "si_monthly_plan",
          "price_id": "price_annual_plan"
        },
        {
          "action": "add",
          "price_id": "price_addon_support",
          "quantity": 1,
          "apply_at_end": false
        }
      ],
      "coupon_changes": [
        {
          "action": "add",
          "coupon_id": "coup_welcome20"
        }
      ]
    }'
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python
    result = client.change_requests.add_changes(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4",
        item_changes=[
            {
                "action": "update",
                "item_id": "si_monthly_plan",
                "price_id": "price_annual_plan"
            },
            {
                "action": "add",
                "price_id": "price_addon_support",
                "quantity": 1,
                "apply_at_end": False
            }
        ],
        coupon_changes=[
            {
                "action": "add",
                "coupon_id": "coup_welcome20"
            }
        ]
    )
    ```
  </Tab>
</Tabs>

## Item change actions

| Action   | Required fields                     | Optional fields                        | Description                                                                                                                                   |
| -------- | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `add`    | `price_id`                          | `quantity`, `apply_at_end`             | Add a new subscription item. If `apply_at_end: true`, the item is created inactive and activates at the next renewal. Quantity defaults to 1. |
| `update` | `item_id`, `price_id` OR `quantity` | `price_id`, `quantity`, `apply_at_end` | Update an existing item's price and/or quantity. If `apply_at_end: true`, the update is deferred to the next renewal.                         |
| `drop`   | `item_id`                           | `apply_at_end`                         | Remove an item. If `apply_at_end: true`, the item is marked for removal at the next renewal.                                                  |

<Info>
  **Immediate vs. scheduled changes:**

  * `apply_at_end: false` (default) — Changes apply immediately with prorated billing
  * `apply_at_end: true` — Changes are scheduled for the end of the current billing period (no proration)

  Scheduled changes use different execution actions internally (`add_scheduled`, `update_scheduled`, `drop_scheduled`) but are transparently handled by the API.
</Info>

## Coupon changes

| Action   | Required fields | Description                                                        |
| -------- | --------------- | ------------------------------------------------------------------ |
| `add`    | `coupon_id`     | Attach a coupon to the subscription. Replaces any existing coupon. |
| `remove` | `coupon_id`     | Remove the specified coupon from the subscription.                 |

## Balance changes

<Note>
  Balance changes (adding credits or debits to customer account balance) are defined in the API schema but **not yet implemented**. Attempting to add balance changes will result in a 501 Not Implemented error during the Apply step.
</Note>

## Response

```json
{
  "change_request": {
    "id": "chg_live_a1b2c3d4",
    "subscription_id": "sub_abc123",
    "status": "draft",
    "item_changes": [
      {
        "action": "update",
        "item_id": "si_monthly_plan",
        "price_id": "price_annual_plan",
        "quantity": null,
        "apply_at_end": false
      },
      {
        "action": "add",
        "price_id": "price_addon_support",
        "quantity": 1,
        "apply_at_end": false
      }
    ],
    "coupon_changes": [
      {
        "action": "add",
        "coupon_id": "coup_welcome20"
      }
    ],
    "balance_changes": []
  },
  "changes_count": 3
}
```

<Tip>
  You can call this endpoint multiple times to incrementally build your change plan. Each call appends to the existing changes.
</Tip>

# Preview a change request

Preview the execution plan and proration amounts for a change request. This is a **pure computation** — no changes are made to the subscription, but the change request transitions from DRAFT to READY status and caches the preview result.

<Tabs>
  <Tab title="API">
    ```bash
    curl -X POST https://api.paymentkit.com/api/{account_id}/change-requests/{change_request_id}/preview \
    -H "Authorization: Bearer sk_live_..."
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python
    preview = client.change_requests.preview(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4"
    )
    ```
  </Tab>
</Tabs>

## Response

```json
{
  "change_request": {
    "id": "chg_live_a1b2c3d4",
    "status": "ready",
    "last_preview": {
      "items_to_add": [
        {"price_id": "price_addon_support", "quantity": 1}
      ],
      "items_to_update": [
        {"item_id": "si_monthly_plan", "price_id": "price_annual_plan", "quantity": null}
      ],
      "items_to_delete": [],
      "coupon_to_add": "coup_welcome20",
      "coupon_to_remove": null,
      "balance_to_apply_atom": 0,
      "proration_credit_atom": -5000,
      "proration_charge_atom": 100000,
      "invoice_total_atom": 95000,
      "execution_plan": {
        "steps": [
          {
            "phase": 1,
            "action": "update",
            "item_external_id": "si_monthly_plan",
            "price_external_id": "price_annual_plan",
            "quantity": null
          },
          {
            "phase": 1,
            "action": "add",
            "price_external_id": "price_addon_support",
            "quantity": 1
          },
          {
            "phase": 2,
            "action": "COUPON_ADD",
            "coupon_external_id": "coup_welcome20"
          }
        ],
        "auto_resolutions": []
      }
    }
  },
  "preview": { /* same as last_preview */ },
  "execution_plan": { /* same as last_preview.execution_plan */ }
}
```

## Preview fields

| Field                             | Description                                                  |
| --------------------------------- | ------------------------------------------------------------ |
| `items_to_add`                    | Items that will be added to the subscription                 |
| `items_to_update`                 | Items that will be updated (price and/or quantity changes)   |
| `items_to_delete`                 | Items that will be removed                                   |
| `coupon_to_add`                   | Coupon ID being attached (null if none)                      |
| `coupon_to_remove`                | Coupon ID being removed (null if none)                       |
| `balance_to_apply_atom`           | Account balance credit/debit amount in atomic units          |
| `proration_credit_atom`           | Total credits for unused time (negative value)               |
| `proration_charge_atom`           | Total charges for new items (positive value)                 |
| `invoice_total_atom`              | Net amount customer will be charged (max of 0 and net\_atom) |
| `execution_plan.steps`            | Ordered list of steps that will be executed during Apply     |
| `execution_plan.auto_resolutions` | Conflicts that were automatically resolved                   |

## Execution plan

The execution plan shows the exact sequence of operations that will be performed when you call Apply. Steps are executed in phase order:

* **Phase 1** — Item changes (ADD, UPDATE, DROP, and their `_scheduled` variants)
* **Phase 2** — Coupon changes (COUPON\_ADD, COUPON\_REMOVE)
* **Phase 3** — Balance changes (BALANCE\_CREDIT\_ADD, etc.) \[not yet implemented]

Each step includes:

* `action` — The operation type (e.g., `add`, `update_scheduled`, `COUPON_ADD`)
* `item_external_id` — ID of the item being modified (for item changes)
* `price_external_id` — New price ID (for ADD/UPDATE)
* `quantity` — New quantity (for ADD/UPDATE, null means keep existing)
* `coupon_external_id` — Coupon ID (for coupon operations)

<Warning>
  **Conflict detection:** If the preview detects unresolvable conflicts (e.g., trying to update and delete the same item), it returns a 409 Conflict error with details about the conflicting changes. You must modify the change request to resolve these conflicts before calling Apply.
</Warning>

# Apply a change request

Execute the change plan with the **Charge First pattern**: payment is collected BEFORE any subscription changes are made. If payment fails, the subscription remains unchanged and the change request stays in READY status for retry.

<Tabs>
  <Tab title="API">
    ```bash
    curl -X POST https://api.paymentkit.com/api/{account_id}/change-requests/{change_request_id}/apply \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "payment_method_id": "pm_card123"
    }'
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python
    result = client.change_requests.apply(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4",
        payment_method_id="pm_card123"  # optional override
    )
    ```
  </Tab>
</Tabs>

## Request fields

| Field               | Type   | Required | Description                                                                                        |
| ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |
| `payment_method_id` | string | No       | Optional payment method to use instead of the subscription's default. Must belong to the customer. |

## Charge First pattern

The Apply operation follows a strict three-phase pattern to ensure payment succeeds before making any changes:

### Phase 1: Validate & Prepare

* Verify change request is in READY status
* Load subscription, items, and prices
* Build execution plan and verify no conflicts
* Calculate invoice total from cached preview

### Phase 2: Payment (idempotent)

**If invoice total > 0:**

1. Mark payment as being attempted (checkpoint)
2. Create proration invoice with line items
3. Finalize invoice to OPEN status
4. Create payment intent
5. Attempt payment via MIT orchestrator
6. **If payment fails:** Mark payment\_attempted=true, raise error, stay in READY
7. **If payment succeeds:** Mark payment\_recorded=true (checkpoint), proceed to Phase 3

**If invoice total ≤ 0:**

* Zero invoice: Mark as no\_payment\_required, proceed to Phase 3
* Negative invoice (downgrade): Issue credit note, proceed to Phase 3

**Idempotency:** On retry, if `payment_recorded=true`, skip payment collection entirely and proceed directly to Phase 3

### Phase 3: Execute Changes

* Execute all steps from execution plan in phase order
* Handle item ADD/UPDATE/DROP operations (immediate and scheduled)
* Apply coupon changes
* Cancel original subscription if all items removed
* Transition change request to APPLIED status

<Info>
  **Why Charge First?**

  * **Prevents orphaned state** — customer never has new items without payment
  * **Atomic success** — either payment succeeds AND changes apply, or nothing happens
  * **Retry safety** — if network fails after payment but before database commit, retry won't double-charge
</Info>

## Response (success)

```json
{
  "change_request": {
    "id": "chg_live_a1b2c3d4",
    "status": "applied",
    "applied_at": "2026-04-14T10:30:00Z"
  },
  "result": {
    "subscription_external_id": "sub_abc123",
    "new_subscriptions": [],
    "invoice_external_id": "inv_proration456",
    "payment_status": "paid",
    "step_results": [
      {
        "phase": 1,
        "action": "update",
        "item_external_id": "si_monthly_plan",
        "result": "success"
      },
      {
        "phase": 1,
        "action": "add",
        "item_external_id": null,
        "result": "success"
      },
      {
        "phase": 2,
        "action": "COUPON_ADD",
        "result": "success"
      }
    ]
  }
}
```

## Response (payment failed)

When payment fails, the API returns a 402 Payment Required error:

```json
{
  "error": "payment_failed",
  "message": "Payment failed for change plan",
  "payment_status": "failed",
  "payment_error": "Your card was declined. Please try a different payment method.",
  "orchestrator_summary": "Card declined by issuer (insufficient_funds)"
}
```

The change request remains in READY status. You can retry the Apply operation:

* With a different payment method
* After the customer updates their payment method
* Same request ID works — idempotency prevents double-charging

## Payment status values

| Status                | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `paid`                | Payment succeeded                                         |
| `processing`          | Async payment (ACH/SEPA) submitted, awaiting confirmation |
| `failed`              | Payment failed                                            |
| `requires_action`     | Customer action required (e.g., 3D Secure)                |
| `no_payment_method`   | No payment method available                               |
| `already_paid`        | Payment was already recorded (idempotent retry)           |
| `no_payment_required` | Zero or negative invoice (no payment needed)              |

<Warning>
  **3D Secure and requires\_action:** If payment requires customer action (3D Secure authentication), the API returns `payment_status: "requires_action"` with a `payment_intent_id`. You must redirect the customer to complete authentication, then retry the Apply call after authentication succeeds. The idempotency mechanism ensures the customer isn't double-charged.
</Warning>

# Cancel a change request

Cancel a draft or ready change request. Once applied, a change request cannot be cancelled.

<Tabs>
  <Tab title="API">
    ```bash
    curl -X DELETE https://api.paymentkit.com/api/{account_id}/change-requests/{change_request_id} \
    -H "Authorization: Bearer sk_live_..."
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python
    result = client.change_requests.cancel(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4"
    )
    ```
  </Tab>
</Tabs>

## Response

```json
{
  "id": "chg_live_a1b2c3d4",
  "status": "cancelled",
  "cancelled_at": "2026-04-14T11:00:00Z"
}
```

<Info>
  Cancelled change requests are retained for audit purposes. The subscription can now have a new change request created.
</Info>

# Status transitions

Change requests follow a strict state machine:

```
DRAFT → READY → APPLIED
  ↓       ↓
CANCELLED CANCELLED
  ↓
EXPIRED (after expires_at)
```

| Status      | Description                       | Allowed operations           |
| ----------- | --------------------------------- | ---------------------------- |
| `draft`     | Initial state after creation      | Add changes, Preview, Cancel |
| `ready`     | Preview completed, ready to apply | Apply, Cancel                |
| `applied`   | Successfully executed             | None (terminal)              |
| `cancelled` | Manually cancelled                | None (terminal)              |
| `expired`   | Draft expired before preview      | None (terminal)              |

<Warning>
  **Adding changes to READY:** If you call Add Changes on a READY change request, it transitions back to DRAFT. You must call Preview again before Apply.
</Warning>

# Proration calculation

Proration is calculated for **immediate changes only** (not scheduled changes). The calculation depends on the change type:

## DROP (remove item)

**Credit for unused time at current price**

```
credit = (days_remaining / days_in_period) × item_price × quantity
```

Example: Remove a \$100/month item with 15 days remaining in a 30-day period

* Credit: (15/30) × $100 = $50

## UPDATE (change price or quantity)

**Credit for old price + Charge for new price**

```
credit = (days_remaining / days_in_period) × old_price × old_quantity
charge = (days_remaining / days_in_period) × new_price × new_quantity
net = charge - credit
```

Example: Upgrade from $100/month to $200/month with 15 days remaining

* Credit: (15/30) × $100 = $50
* Charge: (15/30) × $200 = $100
* Net: $100 - $50 = \$50

## ADD (new item)

**Charge for remaining time at new price**

```
charge = (days_remaining / days_in_period) × price × quantity
```

Example: Add a \$50/month add-on with 15 days remaining

* Charge: (15/30) × $50 = $25

## Scheduled changes (apply\_at\_end: true)

**No proration** — scheduled changes don't affect the current period billing. They execute at the next renewal with no mid-cycle adjustments.

## Downgrade handling

When the net proration is negative (more credits than charges), PaymentKit handles it automatically:

* **Negative net \< 0:** Issue a credit note to customer balance immediately
* **Zero net = 0:** No invoice created, changes apply without payment
* **Positive net > 0:** Create invoice and collect payment

Example: Downgrade from $200/month to $100/month with 15 days remaining

* Credit: (15/30) × $200 = $100
* Charge: (15/30) × $100 = $50
* Net: $50 - $100 = -$50 → Issue $50 credit note

**Response fields for downgrades:**

```json
{
  "change_request": {...},
  "result": {
    "subscription_id": "sub_abc123",
    "invoice_id": null,
    "credit_note_id": "cn_abc123",
    "payment_status": "not_required",
    "new_subscriptions": [],
    "step_results": [...]
  }
}
```

# Common workflows

## Preview before showing to customer

Build a change request, preview it, show the preview to the customer, then apply only after they confirm:

```python
# 1. Create draft
change_request = client.change_requests.create(
    account_id="acc_abc123",
    subscription_id="sub_abc123"
)

# 2. Add changes
client.change_requests.add_changes(
    account_id="acc_abc123",
    change_request_id=change_request["id"],
    item_changes=[{
        "action": "update",
        "item_id": "si_monthly_plan",
        "price_id": "price_annual_plan"
    }]
)

# 3. Preview
preview = client.change_requests.preview(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)

# 4. Show preview to customer
print(f"Proration credit: ${preview['preview']['proration_credit_atom'] / 100}")
print(f"Proration charge: ${preview['preview']['proration_charge_atom'] / 100}")
print(f"Total to charge: ${preview['preview']['invoice_total_atom'] / 100}")

# 5. Customer confirms, apply
result = client.change_requests.apply(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)

if result["result"]["payment_status"] == "paid":
    print("Success! Subscription updated.")
else:
    print(f"Payment failed: {result['result'].get('payment_error')}")
```

## Handle payment failure with retry

```python
try:
    result = client.change_requests.apply(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4"
    )
except PaymentFailedError as e:
    # Change request stays in READY status
    # Prompt customer to update payment method
    print(f"Payment failed: {e.payment_error}")

    # After customer updates payment method, retry with new PM
    result = client.change_requests.apply(
        account_id="acc_abc123",
        change_request_id="chg_live_a1b2c3d4",
        payment_method_id="pm_new_card"
    )
    # Idempotency ensures no double-charging
```

## Batch multiple changes atomically

```python
# Create draft
change_request = client.change_requests.create(
    account_id="acc_abc123",
    subscription_id="sub_abc123"
)

# Add all changes in one call
client.change_requests.add_changes(
    account_id="acc_abc123",
    change_request_id=change_request["id"],
    item_changes=[
        {"action": "update", "item_id": "si_basic", "price_id": "price_pro"},
        {"action": "add", "price_id": "price_addon_storage", "quantity": 2},
        {"action": "drop", "item_id": "si_old_addon"}
    ],
    coupon_changes=[
        {"action": "add", "coupon_id": "coup_enterprise20"}
    ]
)

# Preview and apply
preview = client.change_requests.preview(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)

result = client.change_requests.apply(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)
```

All changes are applied atomically — either all succeed or none are applied.

## Schedule changes for end of period

```python
# Create and add changes
change_request = client.change_requests.create(
    account_id="acc_abc123",
    subscription_id="sub_abc123"
)

client.change_requests.add_changes(
    account_id="acc_abc123",
    change_request_id=change_request["id"],
    item_changes=[
        {
            "action": "update",
            "item_id": "si_monthly_plan",
            "price_id": "price_annual_plan",
            "apply_at_end": True  # Schedule for period end
        }
    ]
)

# Preview (no proration for scheduled changes)
preview = client.change_requests.preview(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)
# proration_credit_atom and proration_charge_atom will be 0

# Apply (creates pending change on subscription)
result = client.change_requests.apply(
    account_id="acc_abc123",
    change_request_id=change_request["id"]
)
# payment_status will be "no_payment_required"
```

The change executes automatically at the subscription's `current_period_end`.

# Comparison with other endpoints

| Feature                    | Change Request              | Update Items                                      | Change Plan (deprecated)            |
| -------------------------- | --------------------------- | ------------------------------------------------- | ----------------------------------- |
| **Preview before apply**   | ✅ Explicit preview step     | ❌ No preview                                      | ❌ No preview                        |
| **Payment before changes** | ✅ Guaranteed (Charge First) | ✅ Guaranteed (Charge First with `always_invoice`) | ✅ Guaranteed (Charge First, always) |
| **Idempotent retry**       | ✅ Built-in                  | ❌ No                                              | ❌ No                                |
| **Status tracking**        | ✅ DRAFT → READY → APPLIED   | ❌ No                                              | ❌ No                                |
| **Multi-step workflow**    | ✅ Create → Preview → Apply  | ❌ Single call                                     | ❌ Single call                       |
| **Scheduled changes**      | ✅ `apply_at_end: true`      | ✅ `start_at_end`/`drop_at_end`                    | ✅ `effective_at: "period_end"`      |
| **Same-interval changes**  | ✅ Supported                 | ✅ Primary use case                                | ⚠️ Supported but not recommended    |
| **Cross-interval changes** | ✅ Supported                 | ❌ Not supported                                   | ✅ Primary use case                  |
| **Conflict detection**     | ✅ Automatic in Preview      | ❌ No                                              | ❌ No                                |
| **Deprecation status**     | ✅ Current                   | ✅ Current                                         | ⚠️ Planned for deprecation          |

**When to use:**

* **Change Request** (recommended) — When you need explicit preview before applying changes, multi-step workflow with user confirmation, or idempotent retry safety
* **Update Items** — For simple same-interval changes when preview and multi-step workflow aren't needed
* **Change Plan** (deprecated) — Legacy endpoint for cross-interval changes. Use Change Request instead for new integrations.

**Note:** All three endpoints use Charge First pattern to guarantee payment succeeds before modifying subscriptions. The key differences are preview capability, workflow complexity, and retry safety.

# Webhooks

## Immediate changes

When a change request is applied with immediate changes:

1. `subscription.change_request.created` — Change request created (DRAFT)
2. `subscription.change_request.previewed` — Preview completed (READY)
3. `invoice.created` — Proration invoice created (if invoice\_total > 0)
4. `invoice.paid` — Payment succeeded (if invoice\_total > 0)
5. `customer.subscription.updated` — Subscription items modified
6. `subscription.change_request.applied` — Change request applied (APPLIED)

## Scheduled changes

When a change request includes scheduled changes (`apply_at_end: true`):

1. Change request webhooks fire as normal (created, previewed, applied)
2. `customer.subscription.updated` — Pending changes stored on subscription
3. At period end, when the scheduled change executes:
   * `customer.subscription.updated` — Items activated/deactivated/updated

## Failed payment

When payment fails during Apply:

1. `subscription.change_request.payment_failed` — Payment failed, still in READY
2. Customer updates payment method
3. Retry Apply → same sequence as successful payment

<Warning>
  **Delivery order is NOT guaranteed.** Design webhook handlers to be idempotent and don't assume ordering.
</Warning>