> For the complete documentation index, see [llms.txt](https://docs.commercelayer.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.commercelayer.io/core-api-reference/2026-05/payment_sessions.md).

# Payment sessions

The payment session object and the allowed CRUD operations on the related resource endpoint

A payment session represents a payment intent within Commerce Layer's Payments API — the central object of the payment flow, tracking the full lifecycle of a payment from creation through [authorization](/core-api-reference/2026-05/payment_authorizations.md), [capture](/core-api-reference/2026-05/payment_captures.md), [void](/core-api-reference/2026-05/payment_voids.md), and [refund](/core-api-reference/2026-05/payment_refunds.md) — and is always associated with a [payment setting](/core-api-reference/2026-05/payment_settings.md) that determines the underlying payment provider.

A payment session can optionally be linked to an [order](/core-api-reference/2026-05/orders.md) or initiated independently via a [payment link](/core-api-reference/2026-05/payment_links.md).

{% hint style="success" %}
When associated with an order, multiple sessions can be created simultaneously — each backed by a different payment setting type — allowing customers to split the order total across different payment instruments (e.g. part gift card, part card payment).
{% endhint %}

When linked to an order, `amount_cents` can be set explicitly at creation time and is capped at the order's remaining `session_amount_cents` — the portion of the order total not yet covered by sessions in `authorized`, `paid`, or `partially_paid` status. When initiated via a payment link instead, it takes the link's own amount and isn't capped, since that amount is what the customer was already charged at the gateway.

{% hint style="warning" %}
This creation-time cap is a convenience, not a guarantee: when multiple sessions are created for the same order in quick succession, each one is only capped against what's already been authorized *at that moment* — a still unpaid sibling isn't counted yet. The authoritative check [happens later](#exceeding-the-order-balance), at authorization time.
{% endhint %}

{% hint style="info" %}
A payment session cannot be created for a free order (total amount zero).
{% endhint %}

A payment session can optionally be linked to a [payment wallet](/core-api-reference/2026-05/payment_wallets.md), allowing customers to [reuse vaulted payment instruments](#paying-with-a-wallet) for faster checkout.

{% hint style="warning" %}
A payment session cannot be created for an order that already has a legacy ([deprecated](/core-api-reference/2026-05/readme.md#whats-deprecated) after version `2017-08`) payment source and/or payment method engaged — associating either one first ties the order to that version's payments workflow, and creating a payment session afterward raises an unsupported-version error (the reverse case is covered [here](/core-api-reference/2026-05/orders.md#payment-sessions)).
{% endhint %}

## Lifecycle

A payment session transitions through the following statuses:

<table><thead><tr><th width="220">Status</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>unpaid</code></strong></td><td>Initial state — payment has not been processed yet.</td></tr><tr><td><strong><code>authorized</code></strong></td><td>An authorization has been successfully recorded.</td></tr><tr><td><strong><code>voided</code></strong></td><td>The authorization was cancelled before capture.</td></tr><tr><td><strong><code>invalidated</code></strong></td><td>An authorization attempt ended in failure — either a previously succeeded authorization was reversed by the payment provider (e.g. a delayed decline), or, for gift cards, the attempt failed outright before ever succeeding — distinct from a deliberate void.</td></tr><tr><td><strong><code>paid</code></strong></td><td>The full session amount has been captured.</td></tr><tr><td><strong><code>partially_paid</code></strong></td><td>A partial capture has been recorded.</td></tr><tr><td><strong><code>refunded</code></strong></td><td>The full captured amount has been refunded.</td></tr><tr><td><strong><code>partially_refunded</code></strong></td><td>A partial refund has been issued.</td></tr></tbody></table>

{% hint style="warning" %}
The `invalidated` status is reached only as a side effect of a failed or reversed authorization attempt, never a direct API action — either the provider's own initial success turned out to be a mistake (more details [here](/core-api-reference/2026-05/payment_authorizations.md#invalidation)), or, for gift cards, the authorization attempt failed outright before ever succeeding (more details [here](/core-api-reference/2026-05/payment_setting_gift_cards.md#invalidation-at-authorization)). Like `voided`, it's a terminal status: no further transaction can be created against the session.
{% endhint %}

## Payment types

The session's payment subtype is derived automatically from the associated payment setting and determines which payment provider handles the transaction. Supported types are:

* [Adyen](/core-api-reference/2026-05/payment_setting_adyens.md) — processes payments through the Adyen gateway.
* [Braintree](/core-api-reference/2026-05/payment_setting_braintrees.md) — processes payments through Braintree's GraphQL API.
* [Checkout.com](/core-api-reference/2026-05/payment_setting_checkout_coms.md) — processes payments through the Checkout.com gateway.
* [External](/core-api-reference/2026-05/payment_setting_externals.md) — integrates any custom gateway via a webhook-based contract.
* [Gift card](/core-api-reference/2026-05/payment_setting_gift_cards.md) — redeems a Commerce Layer gift card as payment.
* [Manual](/core-api-reference/2026-05/payment_setting_manuals.md) — handles off-band methods like wire transfers, with no gateway calls.
* [PayPal](/core-api-reference/2026-05/payment_setting_paypals.md) — processes payments through PayPal's REST API.
* [Stripe](/core-api-reference/2026-05/payment_setting_stripes.md) — processes payments through the Stripe gateway.

{% hint style="info" %}
When using a gift card payment setting, the session requires a `gift_card_code` attribute. The code is validated against available active gift cards scoped to the order's market or currency.
{% endhint %}

## Expiration

A payment session carries an `expires_at` timestamp. When you don't supply one, it's set automatically from the expiration window of the payment setting behind the session:

* PayPal — **3 hours**.
* Adyen and Checkout.com — **1 day**.
* Braintree — **2 days**.
* Stripe — **7 days**, only when `auto_capture` is disabled. With `auto_capture` enabled there's no default window, same as gift card and manual below.
* External — whatever you supply, since there's no window Commerce Layer can assume for a custom gateway.
* Gift card and manual — **none**. Along with Stripe under `auto_capture`, these are the settings with no default window — set `expires_at` explicitly if you want the session to expire anyway.

{% hint style="info" %}
An expiration date you set explicitly always wins over the gateway default.
{% endhint %}

{% hint style="warning" %}
Expiration blocks every transaction type, not only a retried authorization: no payment authorization, capture, void or refund can be created against an expired session. It also reaches further than the terminal `voided` and `invalidated` statuses. Those stop new transactions from being created, while expiration additionally stops the ones already in flight from moving — a transaction still `pending` or `processing` when the session expires can no longer reach a final status, including when the update would have come from a gateway webhook.
{% endhint %}

## Exceeding the order balance

When a session is linked to an order, Commerce Layer checks whether authorizing it would push the combined amount from sessions in `authorized`, `paid`, or `partially_paid` status past the order's total.

{% hint style="info" %}
This check happens **at authorization time**, not at session creation — so it also covers the case where multiple sessions for the same order (e.g. a split payment across a gift card and a card, or several sessions created in quick succession) are authorized close together and would otherwise combine to over-collect.
{% endhint %}

If authorizing a session would push the order over its total, the session still authorizes normally — its lifecycle and status are unaffected. Instead, its `balance_exceeded_at` attribute is stamped with the time this was detected. The underlying payment authorization is never rejected or falsified: it stays `succeeded`, since the authorization genuinely happened with the payment provider (and, depending on the provider and auto-capture setting, may already have placed a hold or even captured funds).

{% hint style="warning" %}
Reconciling this is your responsibility, and you decide which session to adjust — it doesn't have to be the one whose balance-exceeded timestamp was set. Create a payment void, a payment refund, or a *partial* payment capture (keeping only part of what was authorized and letting the rest lapse) against whichever session's authorization or capture you want to adjust, using the entirely standard flows. No session is treated any differently for having been flagged.
{% endhint %}

{% hint style="info" %}
The `balance_exceeded_at` attribute is [filterable](/core-api-reference/2026-05/payment_sessions/list.md#filterable-fields), so you can query directly for flagged sessions (e.g. to build a reconciliation view) instead of comparing each session's amount against the order total yourself.
{% endhint %}

## Capture and refund balances

A session tracks two running balance attributes that reflect the current state of all associated transactions:

* `capture_balance_cents` — the amount remaining to be captured (session amount minus the amounts of the succeeded captures). Refunds don't change it: refunded money gives funds back to the customer, it doesn't make them capturable again.
* `refund_balance_cents` — the amount remaining to be refunded (session amount minus already refunded amounts).

{% hint style="info" %}
There is no `authorization_balance_cents` — a session can hold exactly one succeeded payment authorization and one succeeded payment void (failed attempts can be retried — [authorization](/core-api-reference/2026-05/payment_authorizations.md#retrying-a-failed-attempt), [void](/core-api-reference/2026-05/payment_voids.md#retrying-a-failed-attempt) — and don't count), so the authorized amount is always equal to the full session amount. Multiple captures and multiple refunds are supported instead, which is why the two attributes above exist as running balances.
{% endhint %}

## Gateway options

At creation time, a payment session accepts an `options` object whose values are passed straight through to the underlying payment provider, alongside the attributes Commerce Layer manages automatically. This lets you use gateway-specific parameters without a dedicated Commerce Layer field for each one — including overriding some of the fields Commerce Layer would otherwise compute on your behalf, such as Adyen's `recurringProcessingModel` or `shopperInteraction`.

Supported keys depend on the payment setting type:

* **Braintree** — any field accepted by Braintree's `TransactionInput` GraphQL type, e.g. `descriptor` (dynamic statement descriptor — `name`, `phone`, `url`), `orderId`, `purchaseOrderNumber`, `discountAmount`, `shipping`, `tax`, `lineItems`, `riskData`, `customerDetails`.
* **Stripe** — any parameter accepted when creating a `PaymentIntent`, e.g. `setup_future_usage`, `metadata`, `customer`.
* **Adyen** — any field accepted by the `/payments` request — these are merged into Commerce Layer's own payload, and can override fields Commerce Layer computes by default.

{% hint style="warning" %}
The reconciliation reference and the amount sent to the gateway can never be overridden via options, regardless of what's passed — Commerce Layer always sets these last, taking precedence over any matching key in the `options` object. Fields the gateway doesn't recognize are rejected by the provider itself, not by Commerce Layer.
{% endhint %}

{% hint style="info" %}
The `options` attribute can only be set using [integration](/core/api-credentials.md#integration) API credentials — sales channels cannot supply it. Options are also hidden from sales channel API credentials when reading a session — only integration API credentials can see it in the response.
{% endhint %}

## Requesting a specific internal version

A payment session can force a specific supported [internal payload version](/core-api-reference/2026-05/payment_settings.md#internal-versions) for its own creation request via the `_internal_version` trigger attribute, overriding whatever the payment setting is configured with by default — without needing to set any [gateway options](#gateway-options) and without requiring integration API credentials.

The supported values depend on the payment setting type — check the internal versions listed on each payment setting's page (e.g. [Adyen](/core-api-reference/2026-05/payment_setting_adyens.md#internal-versions)):

<pre class="language-json"><code class="lang-json">{
  "data": {
    "type": "payment_sessions",
    "attributes": {
<strong>      "_internal_version": "..."
</strong>    },
    "relationships": {
      "order": { "data": { "type": "orders", "id": "..." } },
      "payment_setting": { "data": { "type": "payment_settings", "id": "..." } }
    }
  }
}
</code></pre>

{% hint style="info" %}
Unlike `options`, `_internal_version` is available to [sales channel](/core/api-credentials.md#sales-channel) API credentials too — it only lets the caller pick among payload variants Commerce Layer has already built, never raw gateway parameters. An unsupported value is rejected with a `422 Unprocessable Entity` error.
{% endhint %}

## Payment instrument

A session's `payment_instrument` attribute describes the instrument the customer actually paid with (e.g. card brand and last digits, or the PayPal account), so you can show it on order confirmations, receipts, or in your back office without querying the payment provider.

It's filled once, as soon as the session's authorization succeeds — whether synchronously or through a webhook — from the data the provider returns:

<table><thead><tr><th width="180">Payment setting</th><th>Source</th></tr></thead><tbody><tr><td><strong>Adyen</strong></td><td>The payment (or payment details) response, or the <code>AUTHORISATION</code> notification.</td></tr><tr><td><strong>Braintree</strong></td><td>The payment method snapshot of the authorized (or charged) transaction.</td></tr><tr><td><strong>Checkout.com</strong></td><td>The payment <code>source</code>, from the payment response or the <code>payment_approved</code> webhook.</td></tr><tr><td><strong>PayPal</strong></td><td>The payer of the authorized (or captured) order.</td></tr><tr><td><strong>Stripe</strong></td><td>The payment method of the payment intent.</td></tr><tr><td><strong>External</strong></td><td>The <code>payment_instrument</code> object returned by your authorization endpoint (more details <a href="/core-api-reference/2026-05/payment_setting_externals.md#payment-instrument">here</a>).</td></tr></tbody></table>

{% hint style="info" %}
When the session is [paid with a wallet](#paying-with-a-wallet), or [stores the instrument](#vaulting-during-a-charge) while being paid, the instrument is also taken from the wallet, if it isn't already set.
{% endhint %}

Depending on the payment method, the object includes some of the following keys:

<table><thead><tr><th width="220">Key</th><th>Description</th></tr></thead><tbody><tr><td><code>issuer_type</code></td><td>The kind of instrument (e.g. <code>card</code>, <code>scheme</code>, <code>paypal</code>, <code>applepay</code>).</td></tr><tr><td><code>card_type</code></td><td>The card brand (e.g. <code>visa</code>).</td></tr><tr><td><code>card_last_digits</code></td><td>The last digits of the card number.</td></tr><tr><td><code>card_expiry_month</code>, <code>card_expiry_year</code></td><td>The card expiration date.</td></tr><tr><td><code>card_holder_name</code></td><td>The cardholder name.</td></tr><tr><td><code>card_fingerprint</code></td><td>The provider fingerprint identifying the card.</td></tr><tr><td><code>account_email</code>, <code>account_id</code></td><td>The account email and identifier (e.g. PayPal payer).</td></tr><tr><td><code>account_last_digits</code>, <code>account_type</code>, <code>bank_name</code></td><td>The bank account details (e.g. US bank accounts on Braintree).</td></tr><tr><td><code>account_holder_type</code></td><td>The account holder type (e.g. <code>individual</code> or <code>company</code> on Stripe, <code>personal</code> or <code>business</code> on Braintree, or the raw account owner name on Adyen).</td></tr><tr><td><code>account_fingerprint</code></td><td>The provider fingerprint identifying the bank account.</td></tr><tr><td><code>username</code>, <code>venmo_user_id</code></td><td>The Venmo username and user identifier.</td></tr><tr><td><code>issuer</code></td><td>The instrument issuer, as returned by an external integration.</td></tr><tr><td><code>account_status</code></td><td>The account status, as returned by an external integration.</td></tr><tr><td><code>payment_id</code></td><td>The provider identifier of the payment method (e.g. the Stripe payment method ID).</td></tr></tbody></table>

```json
{
  "payment_instrument": {
    "issuer_type": "card",
    "card_type": "visa",
    "card_last_digits": "4242",
    "card_expiry_month": 12,
    "card_expiry_year": 2030
  }
}
```

{% hint style="info" %}
Once set, the payment instrument isn't overwritten by later events or wallets. It stays empty when the provider returns no instrument data — e.g. for local payment methods, or for Adyen when the card details aren't enabled among the additional data returned by your Adyen account.
{% endhint %}

## Paying with a wallet

A session's `payment_wallet` relationship links it to a previously vaulted payment wallet, letting a returning customer pay with a saved instrument instead of collecting payment details again through the provider's client-side SDK.

When a wallet is linked, the session uses the wallet's `payment_token` and `customer_token` directly to build the gateway request, in place of the payment details collected client-side. On providers that support it (e.g. Adyen), a wallet-backed session also skips the SCA/3DS challenge, since the instrument was already verified when the wallet was vaulted.

On a target order generated by an [order subscription](/core-api-reference/2026-05/order_subscriptions.md#unattended-renewals), a wallet-backed session is treated as merchant-initiated: the gateway is told the customer is not present, and the stored instrument is charged off session, with no authentication step.

To link a wallet, the following conditions must all be met:

* The wallet must be in `succeeded` status.
* If the wallet has an `expires_at`, it must not be expired.
* The wallet must belong to the same customer as the session's order (a wallet belonging to a different customer is rejected).
* The wallet must use the same `payment_setting` as the session.
* The wallet and the session must belong to the same organization and [environment](/core/api-specification.md#environments) (*test* or *live*).

{% hint style="success" %}
The `payment_wallet` relationship can be set at creation time or attached (or swapped for a different one) on an existing session via update — so a customer can pick or change which saved payment instrument to use.
{% endhint %}

## Vaulting during a charge

A session's `vaulting` attribute asks the gateway to store the payment instrument as part of the charge itself: the request is sent along with the payment, and the instrument is stored only if that payment succeeds. Commerce Layer then creates the corresponding payment wallet from the token the gateway returns and links it to the session — you don't need to create the wallet yourself, and the same instrument can be charged again later without collecting payment details anew.

This is different from the standard payment wallet workflow, where you create the wallet directly and the gateway stores the instrument without charging the customer:

<table><thead><tr><th width="120"></th><th>Vaulting during a charge</th><th>Creating a payment wallet</th></tr></thead><tbody><tr><td><strong>How</strong></td><td>Set the <code>vaulting</code> attribute on the payment session.</td><td>Create a payment wallet for the customer.</td></tr><tr><td><strong>Payment</strong></td><td>Required — the instrument is stored only when the session is paid.</td><td>None — the instrument is stored on its own (e.g. to save a card before any purchase).</td></tr><tr><td><strong>Wallet</strong></td><td>Created automatically by Commerce Layer from the charge, already <code>succeeded</code> and linked to the session.</td><td>Needs to be created explicitly, then linked to the sessions it should pay.</td></tr><tr><td><strong>Gateways</strong></td><td>Adyen, Checkout.com, PayPal, Stripe, external (with a token URL).</td><td>Any vaultable payment setting, Braintree included.</td></tr></tbody></table>

{% hint style="success" %}
Vaulting is supported by Adyen, Checkout.com, PayPal, Stripe, and [external](/core-api-reference/2026-05/payment_setting_externals.md#storing-the-instrument-during-a-payment) payment settings with a `token_url` configured. For Adyen, `vaulting` is also enabled when the client data passed to the session sets `storePaymentMethod` to `true`.
{% endhint %}

{% hint style="warning" %}
Asking for `vaulting` on any other payment setting — including Braintree, whose payment methods are consumed by the charge and can't be stored afterwards — is rejected with a `422 Unprocessable Entity` error: create the wallet explicitly instead. No wallet is created for orders without a customer either, since a wallet always belongs to one.
{% endhint %}

{% hint style="info" %}
PayPal only vaults the account when reference transactions are enabled on your PayPal merchant account. When PayPal completes the payment before storing the account, the wallet is created as soon as the `VAULT.PAYMENT-TOKEN.CREATED` event arrives instead (more details [here](/core-api-reference/2026-05/payment_setting_paypals.md#webhook-events)).
{% endhint %}

{% hint style="warning" %}
Vaulting is never enabled automatically — not even for orders with recurring [line items](/core-api-reference/2026-05/line_items.md#choosing-line-items-that-generate-subscriptions) — since storing a payment instrument requires the shopper's consent, collected by your storefront before creating the session with `vaulting` set to `true` (the attribute can only be set at creation time). Even when requested, it isn't guaranteed: each provider translates it in its own terms and can still refuse, in which case the session is paid all the same but no wallet is created. Either way, an [order subscription](/core-api-reference/2026-05/order_subscriptions.md#instrument-pending-subscriptions) whose source order ends up without a stored instrument or an existing wallet has nothing to renew on, and stays `pending` until one is available.
{% endhint %}

## Session token

When a session is created, a token is generated and shared with your client-side integration. This token is used by the payment provider's front-end SDK to collect payment details in the browser without sensitive data passing through your server.

For payment settings that support session token specification, an [integration](/core/api-credentials.md#integration) can supply a custom token at creation time instead of having one generated.

{% hint style="info" %}
The token must be unique within the organization environment and will serve as the reference identifier for all subsequent gateway events associated with this session.
{% endhint %}

## Triggers

A payment session supports the following trigger attributes:

* `_refresh` — syncs the session data with the payment provider.
* `_additional_data` — fetches supplementary data from the payment provider required to initialize the client-side integration, such as the available ways to pay for the buyer's context (country, amount, currency).
* `_internal_version` — forces a specific supported [internal payload version](#requesting-a-specific-internal-version) for this session's creation request.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.commercelayer.io/core-api-reference/2026-05/payment_sessions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
