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

# Checkout Sessions

> Create secure, hosted checkout experiences for one-time payments and subscriptions with full customization control.

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="#creating-your-first-checkout-session">
    Create your first checkout session in under 5 minutes
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/checkout-sessions/create">
    Full API documentation and interactive testing
  </Card>

  <Card title="Preview Endpoint" icon="eye" href="/api-reference/checkout-sessions/preview">
    Calculate pricing and taxes before creating a session
  </Card>
</CardGroup>

<Info>
  **Session Validity**: Checkout sessions expire after 24 hours by default, or 15 minutes when `confirm: true`.
</Info>

<Warning>
  **Single-Use Links**: The `checkout_url` is not reusable. Generate a fresh session for each customer and payment attempt rather than sharing or reusing a link.
</Warning>

## Prerequisites

You need:

* An active Dodo Payments merchant account
* API credentials from **Developer → API Keys** in the dashboard
* At least one product created in **Products**

## Creating Your First Checkout Session

<Tabs>
  <Tab title="Node.js SDK">
    ```javascript expandable theme={null}
    import DodoPayments from 'dodopayments';

    const client = new DodoPayments({
      bearerToken: process.env.DODO_PAYMENTS_API_KEY,
      environment: 'test_mode',
    });

    async function createCheckoutSession() {
      const session = await client.checkoutSessions.create({
        product_cart: [
          {
            product_id: 'pdt_123',
            quantity: 1
          }
        ],
        customer: {
          email: 'customer@example.com',
          name: 'John Doe',
          phone_number: '+1234567890'
        },
        billing_address: {
          street: '123 Main St',
          city: 'San Francisco',
          state: 'CA',
          country: 'US',
          zipcode: '94102'
        },
        return_url: 'https://yoursite.com/checkout/success'
      });

      console.log('Checkout URL:', session.checkout_url);
      return session;
    }
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python expandable theme={null}
    import os
    from dodopayments import DodoPayments

    client = DodoPayments(
        bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
        environment="test_mode",
    )

    def create_checkout_session():
        session = client.checkout_sessions.create(
            product_cart=[
                {
                    "product_id": "pdt_123",
                    "quantity": 1
                }
            ],
            customer={
                "email": "customer@example.com",
                "name": "John Doe",
                "phone_number": "+1234567890"
            },
            billing_address={
                "street": "123 Main St",
                "city": "San Francisco",
                "state": "CA",
                "country": "US",
                "zipcode": "94102"
            },
            return_url="https://yoursite.com/checkout/success"
        )

        print(f"Checkout URL: {session.checkout_url}")
        return session
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://test.dodopayments.com/checkouts \
      -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_cart": [
          {
            "product_id": "pdt_123",
            "quantity": 1
          }
        ],
        "customer": {
          "email": "customer@example.com",
          "name": "John Doe",
          "phone_number": "+1234567890"
        },
        "billing_address": {
          "street": "123 Main St",
          "city": "San Francisco",
          "state": "CA",
          "country": "US",
          "zipcode": "94102"
        },
        "return_url": "https://yoursite.com/checkout/success"
      }'
    ```
  </Tab>
</Tabs>

### API Response

All methods return:

```json theme={null}
{
  "session_id": "cks_Gi6KGJ2zFJo9rq9Ukifwa",
  "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
}
```

Only `session_id` is guaranteed to be present. When `payment_method_id` is provided, the charge processes immediately and `checkout_url` is `null`. Use the returned `payment_id` instead.

When `confirm: true`, the payment is created at session-creation time, and the response also includes `payment_id`, `client_secret`, and `publishable_key` for use with the Dodo Payments checkout SDK.

### Redirect Your Customer

<Steps>
  <Step title="Extract the checkout URL">
    Get `checkout_url` from the API response.
  </Step>

  <Step title="Redirect to checkout">
    Send your customer to the URL:

    ```javascript theme={null}
    window.location.href = session.checkout_url;
    ```

    Alternatively, open in a new window:

    ```javascript theme={null}
    window.open(session.checkout_url, '_blank');
    ```
  </Step>

  <Step title="Handle the return">
    After payment, customers are redirected to your `return_url` with query parameters:

    | Parameter | Type | Condition |
    | - | - | - |
    | `payment_id` | string | Always present for one-time payments |
    | `subscription_id` | string | Always present for subscriptions |
    | `status` | string | Always present |
    | `license_key` | string | Present if the product has license keys enabled. Comma-separated if multiple keys |
    | `email` | string | Present if the customer has an email on record |

    Example redirect:

    ```
    https://yoursite.com/return?payment_id=pay_xxx&status=succeeded&license_key=LK-001&email=customer%40example.com
    ```
  </Step>
</Steps>

<Tip>
  Instead of redirecting, you can embed checkout directly in your page using [Overlay Checkout](/developer-resources/overlay-checkout) (modal), [Inline Checkout](/developer-resources/inline-checkout) (embedded), or [Mobile SDKs](/developer-resources/mobile-integration) (native apps). All consume the same session URL.
</Tip>

### Check Session Status

To check a session's status, call [Get Checkout Session](/api-reference/checkout-sessions/get-checkouts) (`GET /checkouts/{id}`). The response has the session `id`, `created_at`, `customer_email`, and `customer_name`, plus `payment_id` and `payment_status`. Both payment fields are `null` while the customer is still entering details. After the customer submits payment, `payment_status` holds the payment's status, such as `succeeded`, `failed`, or `processing`. Use webhooks as the source of truth for fulfillment.

## Request Body

### Required Fields

<ParamField body="product_cart" type="array" required>
  Array of products to include in the checkout session. Each product must have a valid `product_id` from your dashboard.

  You can combine one-time payment products with one subscription product in the same session. A cart can also hold two or more subscription products, but then it can't hold one-time products. See [Multi-Subscription Cart](#multi-subscription-cart). A cart holds at most 20 products.

  <Expandable title="Product Cart Item Properties">
    <ParamField body="product_id" type="string" required>
      The unique identifier of the product from your dashboard.

      **Example:** `"pdt_123abc456def"`
    </ParamField>

    <ParamField body="quantity" type="integer" required>
      Quantity of the product.

      **Example:** `1` for single item, `3` for multiple quantities
    </ParamField>

    <ParamField body="addons" type="array">
      Array of addons to attach to the product. Only valid for subscription products.

      <Expandable title="Addon Item Properties">
        <ParamField body="addon_id" type="string" required>
          The unique identifier of the addon.
        </ParamField>

        <ParamField body="quantity" type="integer" required>
          Quantity of the addon.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="amount" type="integer">
      Amount the customer pays if pay-what-you-want is enabled on the product. Represented in the lowest denomination of the currency (e.g., cents for USD). For example, `100` = \$1.00.

      Ignored if pay-what-you-want is disabled.
    </ParamField>

    <ParamField body="credit_entitlements" type="array">
      Per-session overrides for credit entitlements already attached to this product. Use this to grant a different number of credits for this single session without creating a separate product.

      Each `credit_entitlement_id` must already be attached to the product. This field overrides only the `credits_amount` granted when this session is fulfilled; product-level credit entitlement settings (expiration, rollover, etc.) still apply.

      <Expandable title="Credit Entitlement Override Properties">
        <ParamField body="credit_entitlement_id" type="string" required>
          ID of the credit entitlement to override. Must already be attached to the product.
        </ParamField>

        <ParamField body="credits_amount" type="string" required>
          Number of credits to grant for this checkout session, overriding the product-level `credits_amount`. Must be greater than zero.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<Tip>
  **Find Your Product IDs**: You can find product IDs in your Dodo Payments dashboard under **Products → View Details**, or by using the [List Products API](/api-reference/products/get-products).
</Tip>

### Optional Fields

<AccordionGroup>
  <Accordion title="Customer Information">
    <ParamField body="customer" type="object">
      Customer information. You can either attach an existing customer using their ID or create a new customer record during checkout.

      <Tabs>
        <Tab title="Attach Existing Customer">
          <Expandable title="Existing Customer Properties">
            <ParamField body="customer_id" type="string" required>
              The unique identifier of an existing customer.
            </ParamField>
          </Expandable>
        </Tab>

        <Tab title="Create New Customer">
          <Expandable title="New Customer Properties">
            <ParamField body="email" type="string" required>
              Customer's email address.
            </ParamField>

            <ParamField body="name" type="string">
              Customer's full name as it should appear on receipts and invoices.

              **Example**: `"John Doe"`
            </ParamField>

            <ParamField body="phone_number" type="string">
              Customer's phone number in international format. Required for some payment methods and fraud prevention.

              **Format**: Include country code, e.g., `"+1234567890"`
            </ParamField>
          </Expandable>
        </Tab>
      </Tabs>
    </ParamField>

    <ParamField body="billing_address" type="object">
      Billing address information for accurate tax calculation, fraud prevention, and regulatory compliance.

      When `confirm: true`, all billing address fields become required, unless `minimal_address` is `true`.

      <Expandable title="Billing Address Properties">
        <ParamField body="street" type="string">
          Complete street address including house number, street name, and apartment/unit number if applicable.

          **Example**: `"123 Main St, Apt 4B"`
        </ParamField>

        <ParamField body="city" type="string">
          City or municipality name.

          **Example**: `"San Francisco"`
        </ParamField>

        <ParamField body="state" type="string">
          State, province, or region name. Use full names or standard abbreviations.

          **Example**: `"California"` or `"CA"`
        </ParamField>

        <ParamField body="country" type="string" required>
          Two-letter ISO country code (ISO 3166-1 alpha-2). Always required when billing\_address is provided.

          **Examples**: `"US"`, `"CA"`, `"GB"`, `"DE"`

          <Card title="Country Code Reference" icon="globe" href="/api-reference/misc/supported-countries">
            View complete list of supported countries and their ISO codes
          </Card>
        </ParamField>

        <ParamField body="zipcode" type="string">
          Postal code, ZIP code, or equivalent based on country requirements.

          **Examples**: `"94102"` (US), `"M5V 3A8"` (Canada), `"SW1A 1AA"` (UK)
        </ParamField>
      </Expandable>
    </ParamField>
  </Accordion>

  <Accordion title="Payment Configuration">
    <ParamField body="allowed_payment_method_types" type="array">
      Control which payment methods are available to customers during checkout. This helps optimize for specific markets or business requirements.

      **Common options**: `credit`, `debit`, `upi_collect`, `apple_pay`, `google_pay`, `amazon_pay`, `klarna`, `affirm`, `afterpay_clearpay`, `cashapp`, `ach`, `multibanco`, `bancontact_card`, `eps`, `ideal`, `blik`, `gcash`, `ali_pay_hk`, `fps`, `touch_n_go`, `paypal`

      See the [Create Checkout Session API reference](/api-reference/checkout-sessions/create) for the complete list.

      <Warning>
        Always include `credit` and `debit` as fallback options to prevent checkout failures when preferred payment methods are unavailable.
      </Warning>

      **Example**:

      ```json theme={null}
      ["apple_pay", "google_pay", "credit", "debit"]
      ```
    </ParamField>

    <ParamField body="billing_currency" type="string">
      Override the default currency selection with a fixed billing currency. Uses ISO 4217 currency codes.

      **Supported Currencies**: `USD`, `EUR`, `GBP`, `CAD`, `AUD`, `INR`, and more

      **Example**: `"USD"` for US Dollars, `"EUR"` for Euros

      This field is only effective when adaptive pricing is enabled. If adaptive pricing is disabled, the API discards this field, and the currency comes from the product price or the billing country.
    </ParamField>

    <ParamField body="show_saved_payment_methods" type="boolean" default="false">
      Display previously saved payment methods for returning customers, improving checkout speed and user experience.
    </ParamField>
  </Accordion>

  <Accordion title="Session Management">
    <ParamField body="return_url" type="string">
      URL to redirect customers after payment completion. Dodo Payments appends query parameters to your URL on redirect (see the redirect table above).

      **Example redirect URLs**:

      ```text theme={null}
      # One-time payment with license key
      https://yoursite.com/return?payment_id=pay_xxx&status=succeeded&license_key=LK-001&email=customer%40example.com

      # Subscription payment with multiple license keys
      https://yoursite.com/return?subscription_id=sub_xxx&status=active&license_key=LK-001,LK-002&email=customer%40example.com

      # Payment without license keys
      https://yoursite.com/return?payment_id=pay_xxx&status=succeeded&email=customer%40example.com
      ```

      Use the `license_key` and `email` query parameters to display license keys or send a confirmation immediately on your return page, without needing an extra API call.
    </ParamField>

    <ParamField body="cancel_url" type="string">
      URL to redirect customers when they click the back button or cancel the checkout session. If not provided, the back button will not be displayed.

      Set a `cancel_url` to give customers a clear way to return to your site without completing the purchase.
    </ParamField>

    <ParamField body="confirm" type="boolean" default="false">
      If true, finalizes all session details immediately. The API throws an error if required data is missing.

      When `confirm: true`:

      * All billing address fields become required (only `country` and `zipcode` when `minimal_address` is `true`)
      * `payment_method_id` can be provided to process the charge immediately
      * Session expires after 15 minutes instead of 24 hours
      * An existing `customer_id` is required if `payment_method_id` is provided
    </ParamField>

    <ParamField body="discount_codes" type="array">
      Apply one or more stacked discount codes to the checkout session. Codes are applied in array order (the first code reduces the starting price, the second reduces the already-discounted price, and so on), up to a maximum of 20 codes per session.

      When [Purchasing Power Parity](/features/purchasing-power-parity) is enabled, the starting price is the PPP-adjusted amount, not the base price.

      ```typescript theme={null}
      discount_codes: ['WELCOME10', 'BLACKFRIDAY20']
      ```

      The singular `discount_code` field below is deprecated but still fully supported. It cannot be combined with `discount_codes` in the same request.
    </ParamField>

    <ParamField body="discount_code" type="string" deprecated>
      **Deprecated** — prefer `discount_codes` for new integrations. This field still works for backward compatibility, but cannot be combined with `discount_codes` in the same request.
    </ParamField>

    <ParamField body="metadata" type="object">
      Custom key-value pairs to store additional information about the session.
    </ParamField>

    <ParamField body="force_3ds" type="boolean">
      Override merchant default 3DS behaviour for this session.
    </ParamField>

    <ParamField body="minimal_address" type="boolean">
      Enable minimal address collection mode. When enabled, the checkout only collects:

      * **Country**: Always required for tax determination
      * **ZIP/Postal code**: Only in regions where it's necessary for sales tax, VAT, or GST calculation

      This significantly reduces checkout friction by eliminating unnecessary form fields.

      With `confirm: true`, only `zipcode` (plus `country`) is required; the other billing address fields remain optional.

      Defaults to `true` when `feature_flags.single_page` is `true`, and to `false` otherwise.
    </ParamField>

    <ParamField body="payment_method_id" type="string">
      A saved payment method belonging to the attached customer. Requires `confirm: true` and an existing `customer.customer_id`. The payment method is validated for eligibility with the payment's currency. When set, the charge is processed immediately and `checkout_url` is returned as `null`. Use the returned `payment_id` instead.
    </ParamField>

    <ParamField body="short_link" type="boolean" default="false">
      If true, returns a shortened checkout URL instead of the full session URL.
    </ParamField>

    <ParamField body="product_collection_id" type="string">
      Product collection ID for the collection-based checkout flow. When you set it, pass an empty `product_cart` array. Discount codes can't be pre-applied at session creation. See [Product Collections](/features/checkout#product-collections).
    </ParamField>

    <ParamField body="tax_id" type="string">
      Tax ID for the customer (for example, a VAT number). Requires `billing_address` with a `country`.
    </ParamField>

    <ParamField body="customer_business_name" type="string">
      Optional business or legal name associated with the tax ID, up to 250 characters. When provided together with a valid `tax_id`, it is rendered on the invoice instead of the customer's personal name.
    </ParamField>

    <ParamField body="mandate_min_amount_inr_paise" type="integer">
      Override the merchant-level mandate floor (in INR paise) for INR e-mandates on Indian cards.

      The mandate amount sent to the processor is `max(this_floor, actual_billing_amount)`, so this is effectively the customer-facing authorization ceiling whenever billing is lower. When unset, the merchant setting applies; when that's also unset, the system default of ₹15,000 applies.
    </ParamField>
  </Accordion>

  <Accordion title="UI Customization">
    <ParamField body="customization" type="object">
      Customize the appearance and behavior of the checkout interface.

      <Expandable title="Customization Properties">
        <ParamField body="theme" type="string">
          Theme for the checkout interface. Options: `light`, `dark`, or `system`. If not provided, the checkout uses the theme configured for your business in the dashboard.
        </ParamField>

        <ParamField body="show_order_details" type="boolean" default="true">
          Display order details section in the checkout interface.
        </ParamField>

        <ParamField body="show_on_demand_tag" type="boolean" default="true">
          Show the "on-demand" tag for applicable products.
        </ParamField>

        <ParamField body="force_language" type="string">
          Force the checkout interface to render in a specific language. By default, the checkout auto-detects the customer's browser language.

          **Supported language codes**: `ar`, `ca`, `de`, `en`, `es`, `fr`, `he`, `id`, `it`, `ja`, `ka`, `ko`, `ms`, `nl`, `pl`, `pt`, `ro`, `ru`, `sv`, `th`, `tr`, `zh`

          **Example**: `"es"` for Spanish, `"ja"` for Japanese
        </ParamField>

        <ParamField body="theme_config" type="object">
          Custom theme configuration with colors for light and dark modes, fonts, and button styling. This is the server-side replacement for the deprecated client-side `themeConfig` option.

          <Expandable title="Theme Config Properties">
            <ParamField body="light" type="object">
              Light mode color configuration.
            </ParamField>

            <ParamField body="dark" type="object">
              Dark mode color configuration.
            </ParamField>

            <ParamField body="font_primary_url" type="string">
              URL for the primary font. Must be a valid `https://` URL.
            </ParamField>

            <ParamField body="font_secondary_url" type="string">
              URL for the secondary font. Must be a valid `https://` URL.
            </ParamField>

            <ParamField body="font_size" type="string">
              Font size for the checkout UI. Options: `xs`, `sm`, `md`, `lg`, `xl`, `2xl`.
            </ParamField>

            <ParamField body="font_weight" type="string">
              Font weight for the checkout UI. Options: `normal`, `medium`, `bold`, `extraBold`.
            </ParamField>

            <ParamField body="pay_button_text" type="string">
              Custom text for the pay button (for example, `"Complete Purchase"` or `"Subscribe Now"`). Max 100 characters.
            </ParamField>

            <ParamField body="radius" type="string">
              Border radius for UI elements. A number followed by `px`, `rem`, or `em` (for example, `"4px"`, `"0.5rem"`, `"1em"`).
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>
  </Accordion>

  <Accordion title="Feature Flags">
    <ParamField body="feature_flags" type="object">
      Configure specific features and behaviors for the checkout session.

      <Expandable title="Feature Flags Properties">
        <ParamField body="allow_currency_selection" type="boolean" default="true">
          Allow customers to select their preferred currency during checkout.
        </ParamField>

        <ParamField body="allow_discount_code" type="boolean" default="true">
          Show discount code input field in the checkout interface.
        </ParamField>

        <ParamField body="allow_editing_addons" type="boolean" default="false">
          Allow customers to add or remove addons on a subscription product during checkout. Only applies to subscription products.
        </ParamField>

        <ParamField body="allow_phone_number_collection" type="boolean" default="true">
          Collect customer phone numbers during checkout.
        </ParamField>

        <ParamField body="require_phone_number" type="boolean" default="false">
          Require the customer to provide a phone number to complete checkout. Requires `allow_phone_number_collection` to also be true.
        </ParamField>

        <ParamField body="require_cardholder_name" type="boolean" default="false">
          Require the customer to enter the name on the card to pay by card. Other payment methods ignore this flag.
        </ParamField>

        <ParamField body="allow_tax_id" type="boolean" default="true">
          Allow customers to enter tax identification numbers.
        </ParamField>

        <ParamField body="require_tax_id" type="boolean" default="false">
          Require the customer to provide a Tax ID (GST number in India, VAT number in the EU) before they can complete checkout.

          When enabled, a customer who checks out as a business must provide a Tax ID. Checkout doesn't change for a customer who buys as an individual. See [B2B Payments](/features/b2b-payments#making-the-tax-id-mandatory) for the full checkout behavior.

          `allow_tax_id` must also be `true`. Otherwise, the request is rejected with a `400` and the message `feature_flags.require_tax_id: cannot be true when allow_tax_id is false`. With `confirm: true`, the request must include a non-blank `tax_id`, or it is rejected with a `422`.
        </ParamField>

        <ParamField body="always_create_new_customer" type="boolean" default="false">
          Force creation of a new customer record instead of updating existing ones.
        </ParamField>

        <ParamField body="allow_customer_editing_email" type="boolean">
          Allow customers to edit their email address during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_name" type="boolean">
          Allow customers to edit their name during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_street" type="boolean">
          Allow customers to edit the street address during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_city" type="boolean">
          Allow customers to edit the city during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_state" type="boolean">
          Allow customers to edit the state during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_country" type="boolean">
          Allow customers to edit the country during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_zipcode" type="boolean">
          Allow customers to edit the zipcode during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_tax_id" type="boolean">
          Allow customers to enter or edit their tax ID during checkout.
        </ParamField>

        <ParamField body="allow_customer_editing_business_name" type="boolean" default="false">
          Allow the customer to supply or edit the business name associated with the tax ID. Works independently of `allow_customer_editing_tax_id`: either flag (or `allow_tax_id`) is sufficient to let the customer override the session's business name. Typically set together with `allow_customer_editing_tax_id`.
        </ParamField>

        <ParamField body="redirect_immediately" type="boolean" default="false">
          Skip the default payment success page and redirect customers immediately to your `return_url` after payment completion.

          Use this when you have a custom success page that provides a better user experience, or for [mobile apps](/developer-resources/mobile-integration) and embedded checkout flows.
        </ParamField>

        <ParamField body="single_page" type="boolean" default="false">
          If true, the session uses the single-page checkout flow: the page initializes the payment at load time and confirms it in place, with no separate payment page.
        </ParamField>
      </Expandable>
    </ParamField>
  </Accordion>

  <Accordion title="Custom Fields">
    <ParamField body="custom_fields" type="array">
      Collect additional information from customers during checkout with custom form fields. You can define up to 5 custom fields per checkout session. Customer responses are included in webhook payloads and available via the API.

      <Expandable title="Custom Field Properties">
        <ParamField body="key" type="string" required>
          Unique identifier for this field. Used as the key in webhook payloads and API responses.

          **Example**: `"company_name"`, `"referral_source"`, `"team_size"`
        </ParamField>

        <ParamField body="label" type="string" required>
          Display label shown to the customer on the checkout form.

          **Example**: `"Company Name"`, `"How did you hear about us?"`, `"Team Size"`
        </ParamField>

        <ParamField body="field_type" type="string" required>
          Type of field determining input validation rules.

          **Available types**: `text`, `number`, `email`, `url`, `date`, `dropdown`, `boolean`
        </ParamField>

        <ParamField body="required" type="boolean" default="false">
          Whether this field must be filled before checkout can complete.
        </ParamField>

        <ParamField body="placeholder" type="string">
          Placeholder text displayed in the input field.
        </ParamField>

        <ParamField body="options" type="array">
          List of options for `dropdown` field type. Required when `field_type` is `dropdown`, ignored for other types.

          **Example**: `["Google", "Twitter", "Friend referral", "Other"]`
        </ParamField>
      </Expandable>
    </ParamField>

    Customer responses to custom fields are included in:

    * **Webhooks**: `payment.succeeded`, `subscription.active`, and other relevant event payloads contain the `custom_field_responses` array
    * **API responses**: Payment and subscription objects include `custom_field_responses`
  </Accordion>

  <Accordion title="Subscription Configuration">
    <ParamField body="subscription_data" type="object">
      Additional configuration for checkout sessions containing subscription products.

      <Expandable title="Subscription Data Properties">
        <ParamField body="trial_period_days" type="integer">
          Number of days for the trial period before the first charge. Must be between 0 and 10000 days.

          A paid trial amount cannot be set per session. Configure `trial_amount` on the product's recurring price instead. See [Paid Trials](/features/subscription#paid-trials).
        </ParamField>

        <ParamField body="on_demand" type="object">
          On-demand subscription configuration for usage-based or metered billing.

          <Expandable title="On-Demand Properties">
            <ParamField body="mandate_only" type="boolean" required>
              If set to true, does not perform any charge and only authorizes payment method details for future use.
            </ParamField>

            <ParamField body="product_price" type="integer">
              Product price for the initial charge to customer. If not specified, the stored price of the product is used.

              **Format**: Represented in the lowest denomination of the currency (e.g., cents for USD). For example, `100` = \$1.00.
            </ParamField>

            <ParamField body="product_currency" type="string">
              Optional currency of the product price. If not specified, defaults to the currency of the product.
            </ParamField>

            <ParamField body="product_description" type="string">
              Optional product description override for billing and line items. If not specified, the stored description of the product is used.
            </ParamField>

            <ParamField body="adaptive_currency_fees_inclusive" type="boolean">
              Whether adaptive currency fees should be included in the product price (true) or added on top (false). Ignored if adaptive pricing is not enabled.
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>
  </Accordion>
</AccordionGroup>

## Usage Examples

### Simple Single Product Checkout

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_ebook_guide',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  return_url: 'https://yoursite.com/success'
});
```

### Multi-Product Cart

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_course_advanced',
      quantity: 1
    },
    {
      product_id: 'pdt_certificate',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'Jane Smith'
  },
  return_url: 'https://yoursite.com/success'
});
```

### Multi-Subscription Cart

To sell several subscription products in one checkout, add two or more subscription products to `product_cart`. The customer pays once, and Dodo Payments creates one payment, one invoice, and one subscription for each product. Each subscription has its own billing cycle and renews on its own, so a monthly product and an annual product can share a cart.

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    { product_id: 'pdt_monthly_plan', quantity: 1 },
    { product_id: 'pdt_annual_addon_plan', quantity: 1 }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'Jane Smith'
  },
  return_url: 'https://yoursite.com/success'
});
```

A multi-subscription cart has these limits:

* It can't hold one-time products. The API rejects the request with a `422`.
* It holds at most 20 subscription products.
* It can't list the same product twice. To sell more than one unit of a product, set `quantity` instead.
* It can't use on-demand billing (`subscription_data.on_demand`).
* A discount code applied to the cart counts as one redemption.

`subscription_data.trial_period_days` applies to every subscription in the cart. Without it, each subscription uses the trial set on its own product. When you don't set it and [Prevent Trial Misuse](/features/subscription#preventing-trial-misuse) is on, a product the customer already trialed starts without a trial.

The resulting payment has `is_multi_subscription: true` and lists every subscription it starts in `subscription_ids`. Its `subscription_id` is `null`, because no single subscription owns the payment. The `payment.succeeded` webhook carries the same fields. Read `is_multi_subscription` to find the payment type, not the length of `subscription_ids`.

After the payment succeeds, you receive one `payment.succeeded` event for the cart and one `subscription.active` event for each subscription. From then on, each subscription renews, changes plan, and cancels on its own, with its own invoice for each renewal.

All subscriptions from one cart share the payment method the customer used at checkout. When you [update the payment method](/api-reference/subscriptions/update-payment-method) of one of them, Dodo Payments moves the new payment method to every subscription from the same cart that has the same settlement currency and billing currency and is `active`, `on_hold`, `past_due`, or `paused`. Every subscription from a cart starts with the same settlement currency and billing currency, even when its products have different price currencies. A later change, such as a plan change, can move a subscription to another settlement currency or billing currency. That subscription keeps its old payment method, so update it separately. For an `on_hold` subscription, the update retries only that subscription's unpaid invoice.

### Subscription with Trial Period

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_saas_pro',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  subscription_data: {
    trial_period_days: 14
  },
  return_url: 'https://yoursite.com/success'
});
```

### Pre-Confirmed Checkout

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    customer_id: 'cus_existing_customer_id'
  },
  billing_address: {
    street: '123 Main St',
    city: 'San Francisco',
    state: 'CA',
    country: 'US',
    zipcode: '94102'
  },
  confirm: true,
  return_url: 'https://yoursite.com/success'
});
```

### Checkout with Currency Override

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  billing_currency: 'EUR',
  return_url: 'https://yoursite.com/success'
});
```

### Saved Payment Methods for Returning Customers

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    customer_id: 'cus_existing_customer_id'
  },
  show_saved_payment_methods: true,
  return_url: 'https://yoursite.com/success'
});
```

### B2B Checkout with Tax ID Collection

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_enterprise_plan',
      quantity: 1
    }
  ],
  customer: {
    email: 'procurement@company.com',
    name: 'Jane Smith'
  },
  billing_address: {
    street: '456 Business Ave',
    city: 'London',
    country: 'GB',
    zipcode: 'SW1A 1AA'
  },
  feature_flags: {
    require_tax_id: true
  },
  return_url: 'https://yoursite.com/success'
});
```

### Dark Theme Checkout with Stacked Discount Codes

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  customization: {
    theme: 'dark'
  },
  discount_codes: ['WELCOME10', 'BLACKFRIDAY20'],
  return_url: 'https://yoursite.com/success'
});
```

### Regional Payment Methods (UPI for India)

For detailed information about UPI configuration and testing, see the [India Payment Methods](/features/payment-methods/india) page.

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  billing_address: {
    street: '123 Main St',
    city: 'Bangalore',
    state: 'KA',
    country: 'IN',
    zipcode: '560001'
  },
  billing_currency: 'INR',
  allowed_payment_method_types: ['upi_intent', 'credit', 'debit'],
  return_url: 'https://yoursite.com/success'
});
```

### BNPL (Buy Now Pay Later) Checkout

For detailed information about BNPL configuration and testing, see the [Buy Now Pay Later (BNPL)](/features/payment-methods/bnpl) page.

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  allowed_payment_method_types: ['klarna', 'affirm', 'afterpay_clearpay', 'credit', 'debit'],
  return_url: 'https://yoursite.com/success'
});
```

### Instant Checkout with Existing Payment Method

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    customer_id: 'cus_existing_customer_id'
  },
  payment_method_id: 'pm_existing_payment_method_id',
  confirm: true,
  return_url: 'https://yoursite.com/success'
});
```

### Short Links for Cleaner Payment URLs

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  short_link: true,
  return_url: 'https://yoursite.com/success'
});
```

### Skip Payment Success Page with Immediate Redirect

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  feature_flags: {
    redirect_immediately: true
  },
  return_url: 'https://yoursite.com/success'
});
```

### Forcing a Language

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  customization: {
    force_language: 'es'
  },
  return_url: 'https://yoursite.com/success'
});
```

### Collecting Custom Fields

```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [
    {
      product_id: 'pdt_123',
      quantity: 1
    }
  ],
  customer: {
    email: 'customer@example.com',
    name: 'John Doe'
  },
  custom_fields: [
    {
      key: 'company_name',
      label: 'Company Name',
      field_type: 'text',
      required: true
    },
    {
      key: 'referral_source',
      label: 'How did you hear about us?',
      field_type: 'dropdown',
      options: ['Google', 'Twitter', 'Friend referral', 'Other'],
      required: true
    }
  ],
  return_url: 'https://yoursite.com/success'
});
```

## Previewing Checkout Sessions

Use the [Preview Checkout Session](/api-reference/checkout-sessions/preview) endpoint to calculate pricing, taxes, and totals before creating a session. This is useful for displaying accurate pricing information on your site.

<Note>
  The previewed `current_breakup.subtotal` already reflects [Purchasing Power Parity](/features/purchasing-power-parity) and [Charm Pricing](/features/charm-pricing) where they apply to the product.
</Note>

<Note>
  When the cart contains a subscription product, the preview response also returns a `next_billing_date` — a preview of the upcoming billing date, so you can show it before the subscription is created. It is computed relative to now: `now + trial period` when a trial applies, otherwise `now + one payment frequency`. The field is omitted for one-time-only carts. This is an estimate anchored on the preview time; the authoritative `next_billing_date` is set when the subscription activates.
</Note>

<Note>
  The preview also returns `trial_period_days` (the effective trial length, free or paid) and `trial_amount` (the per-unit trial charge after discounts, in the price currency's minor units). `trial_amount` is only present for a [paid trial](/features/subscription#paid-trials) and is `null` for a free trial or no trial. Use `current_breakup` for the taxed total actually due today.
</Note>

<Note>
  For a [multi-subscription cart](#multi-subscription-cart), the response also returns a `subscriptions` array with one entry per subscription: `product_id`, `amount_due_now`, `tax_due_now`, `recurring_amount`, `recurring_tax`, `trial_period_days`, and `next_billing_date`. The top-level `next_billing_date` is the earliest date in the cart, and the top-level `trial_period_days` and `trial_amount` are `null`. Read amounts from `subscriptions` and `current_breakup`. `recurring_breakup` adds up the renewal amounts of every subscription, even when they renew on different dates, so read each renewal from `subscriptions`.
</Note>

<Tabs>
  <Tab title="Node.js SDK">
    ```javascript expandable theme={null}
    const preview = await client.checkoutSessions.preview({
      product_cart: [
        { product_id: 'pdt_123', quantity: 1 }
      ],
      billing_address: {
        country: 'US',
        state: 'CA',
        zipcode: '94102'
      },
      discount_codes: ['SAVE20']
    });

    console.log('Subtotal:', preview.current_breakup.subtotal);
    console.log('Tax:', preview.current_breakup.tax);
    console.log('Discount:', preview.current_breakup.discount);
    console.log('Total:', preview.current_breakup.total_amount);

    // Present only when the cart contains a subscription product
    console.log('Next billing date:', preview.next_billing_date);
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python expandable theme={null}
    preview = client.checkout_sessions.preview(
        product_cart=[
            {"product_id": "pdt_123", "quantity": 1}
        ],
        billing_address={
            "country": "US",
            "state": "CA",
            "zipcode": "94102"
        },
        discount_codes=["SAVE20"]
    )

    print(f"Subtotal: {preview.current_breakup.subtotal}")
    print(f"Tax: {preview.current_breakup.tax}")
    print(f"Discount: {preview.current_breakup.discount}")
    print(f"Total: {preview.current_breakup.total_amount}")

    # Present only when the cart contains a subscription product
    print(f"Next billing date: {preview.next_billing_date}")
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://test.dodopayments.com/checkouts/preview \
      -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_cart": [
          {
            "product_id": "pdt_123",
            "quantity": 1
          }
        ],
        "billing_address": {
          "country": "US",
          "state": "CA",
          "zipcode": "94102"
        },
        "discount_codes": ["SAVE20"]
      }'
    ```
  </Tab>
</Tabs>

## Migrating from Dynamic Links

If you're using Dynamic Links, Checkout Sessions offer more flexibility. With Dynamic Links, you had to provide the customer's complete billing address. With Checkout Sessions, you can pass whatever information you have, and the checkout flow collects the rest.

For example:

* Provide only the customer's billing country, and checkout collects the remaining details.
* Or provide all information and set `confirm: true` to skip directly to the payment page.

Migrating is straightforward: update your integration to use the Checkout Sessions API or SDK method, adjust the request payload to match the Checkout Sessions format, and you're done. No additional handling is needed.

## Related Resources

<CardGroup cols={2}>
  <Card title="Overlay Checkout" icon="layer-group" href="/developer-resources/overlay-checkout">
    Open checkout as a modal overlay on your page
  </Card>

  <Card title="Inline Checkout" icon="square" href="/developer-resources/inline-checkout">
    Embed checkout directly in your page
  </Card>

  <Card title="Mobile Integration" icon="mobile" href="/developer-resources/mobile-integration">
    Integrate checkout in native mobile apps
  </Card>

  <Card title="Webhooks" icon="webhook" href="/developer-resources/webhooks">
    Listen for payment and subscription events
  </Card>

  <Card title="Payment Methods" icon="credit-card" href="/features/payment-methods">
    Supported payment methods by region
  </Card>

  <Card title="Subscriptions" icon="repeat" href="/features/subscription">
    Recurring billing and subscription management
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.