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

# Payment setting externals

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

External payment settings are the custom gateway subtype of [payment settings](/core-api-reference/2026-05/payment_settings.md). They allow you to integrate any payment provider that is not supported out-of-the-box by implementing a simple webhook-based contract that Commerce Layer calls for each payment operation.

## URLs

Setting up an external payment setting requires the following endpoint URLs:

<table><thead><tr><th width="220">Attribute</th><th>Description</th><th width="100" data-type="checkbox">Required</th></tr></thead><tbody><tr><td><strong><code>authorization_url</code></strong></td><td>Endpoint Commerce Layer calls to authorize a payment.</td><td>true</td></tr><tr><td><strong><code>capture_url</code></strong></td><td>Endpoint called to capture an authorized payment.</td><td>true</td></tr><tr><td><strong><code>void_url</code></strong></td><td>Endpoint called to void an authorization.</td><td>true</td></tr><tr><td><strong><code>refund_url</code></strong></td><td>Endpoint called to issue a refund.</td><td>true</td></tr><tr><td><strong><code>session_url</code></strong></td><td>Endpoint called when a payment session is created — allows the external provider to register the session and return a reference token used to correlate future async events back to the correct session.</td><td>false</td></tr><tr><td><strong><code>token_url</code></strong></td><td>Endpoint called to vault a payment instrument (enables wallet support).</td><td>false</td></tr></tbody></table>

## Capabilities

External payment settings support the following Commerce Layer features:

<table><thead><tr><th width="220">Feature</th><th width="100" data-type="checkbox">Supported</th><th>Notes</th></tr></thead><tbody><tr><td>Payment sessions</td><td>true</td><td></td></tr><tr><td>Payment wallets</td><td>true</td><td>Requires <code>token_url</code> to be configured.</td></tr><tr><td>Payment links</td><td>false</td><td></td></tr><tr><td>3DS / SCA</td><td>true</td><td>Depends on the external provider's implementation.</td></tr></tbody></table>

{% hint style="warning" %}
Unlike the other payment setting subtypes, a checked box here doesn't guarantee unconditional support — just that it *can* be supported, under the condition described in the corresponding note.
{% endhint %}

## Request signing

Commerce Layer signs all outbound requests with [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) using the `shared_secret`. The signature is included in the `X-CommerceLayer-Signature` header so your endpoint can [verify the request authenticity](/core/callbacks-security.md).

## Idempotency

Requests to `authorization_url`, `capture_url`, `void_url`, and `refund_url` carry an `X-CommerceLayer-Idempotency-Key` header. When a new transaction [matches an open one](/core-api-reference/2026-05/payment_transactions.md#idempotency), Commerce Layer sends the same value again, so your endpoint should treat a key it has seen before as the operation it already performed and return the original outcome instead of charging or refunding again. Requests to `session_url` and `token_url` don't carry it.

{% hint style="warning" %}
The header is on every transaction request, including the first one — a key being present says nothing about whether the request is a repeat. Only a key you have already recorded signals a repeat.
{% endhint %}

## Circuit breaker

External payment settings include built-in [circuit breaker](/core/external-resources.md#circuit-breaker) protection. The circuit opens once the number of accumulated failures reaches a threshold of 50 — not an error rate — and any further request is skipped while the circuit remains open. Before that threshold is reached, a successful request resets the failure count back to zero.

The circuit can also be reset manually via the `_reset_circuit` trigger attribute — this is the only way to close it once it has opened.

## Payload enrichment

By default, the request payload sent to your external endpoint includes the order with its market, line items, shipping address, billing address, and customer. You can configure [custom includes](/core/external-resources.md#custom-include-list) via the `options` attribute.

## Payment instrument

To populate the [payment instrument](/core-api-reference/2026-05/payment_sessions.md#payment-instrument) of the session, return a `payment_instrument` object in the successful authorization response `data`, next to the usual transaction fields:

<pre class="language-json"><code class="lang-json">{
  "success": true,
  "data": {
    "transaction_token": "your-external-transaction-token",
    "amount_cents": 1700,
<strong>    "payment_instrument": {
</strong><strong>      "card_type": "visa",
</strong><strong>      "card_last_digits": "4242",
</strong><strong>      "card_expiry_month": 12,
</strong><strong>      "card_expiry_year": 2030
</strong><strong>    }
</strong>  }
}
</code></pre>

Only the following keys are kept — any other key is ignored:

<table><thead><tr><th width="220">Key</th><th>Description</th></tr></thead><tbody><tr><td><code>issuer</code></td><td>The instrument issuer (e.g. the issuing bank).</td></tr><tr><td><code>issuer_type</code></td><td>The kind of instrument. Defaults to <code>external</code> when not provided.</td></tr><tr><td><code>card_type</code></td><td>The card brand.</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>Your fingerprint identifying the card.</td></tr><tr><td><code>account_id</code>, <code>account_email</code>, <code>account_status</code></td><td>The account details, for non-card instruments.</td></tr></tbody></table>

With the example above, the payment session gets:

<pre class="language-json"><code class="lang-json">{
  "payment_instrument": {
<strong>    "issuer_type": "external",
</strong>    "card_type": "visa",
    "card_last_digits": "4242",
    "card_expiry_month": 12,
    "card_expiry_year": 2030
  }
}
</code></pre>

{% hint style="info" %}
The object is read from the authorization response only, the first time the authorization succeeds — returning it on capture, void, or refund has no effect, and it never overwrites an instrument already set. For asynchronous authorizations, include it in the `202` response: webhook events don't carry it.
{% endhint %}

{% hint style="warning" %}
Never return sensitive data such as full card numbers or security codes: they're ignored, but they still travel in the response body.
{% endhint %}

## Wallet vaulting

If a `token_url` is configured, the payment setting is considered *vaultable* and supports payment wallets. Commerce Layer calls the token URL endpoint to vault a payment instrument on behalf of the customer. The external endpoint must return a `payment_source_token` and optionally a `customer_token` in its response `data` to be stored on the wallet — if you already supply it yourself when creating the wallet, that value is kept instead.

{% hint style="info" %}
You can find more information about how this compares to the other payment providers [here](/core-api-reference/2026-05/payment_wallets.md#token-references).
{% endhint %}

### Storing the instrument during a payment

When a [payment session](/core-api-reference/2026-05/payment_sessions.md#vaulting-during-a-charge) has vaulting enabled, the authorization request sent to your `authorization_url` endpoint tells your integration to store the payment instrument. The request body is the payment authorization in JSON:API format, and the session it belongs to is part of its included resources, carrying the `vaulting` flag among its attributes:

<pre class="language-json"><code class="lang-json">{
  "data": {
    "id": "xYZkjABcde",
    "type": "payment_authorizations",
    "attributes": { ... },
    "relationships": {
      "payment_session": {
        "data": { "type": "payment_sessions", "id": "wBXVhKzrrm" }
      },
      ...
    }
  },
  "included": [
    {
      "id": "wBXVhKzrrm",
      "type": "payment_sessions",
      "attributes": {
<strong>        "vaulting": true,
</strong>        ...
      }
    },
    ...
  ]
}
</code></pre>

To have Commerce Layer create the wallet, return the stored instrument in the successful authorization response `data`, next to the usual transaction fields, using the same keys as the token URL endpoint (`payment_source_token` and, optionally, `customer_token`):

<pre class="language-json"><code class="lang-json">{
  "success": true,
  "data": {
    "transaction_token": "your-external-transaction-token",
    "amount_cents": 1700,
<strong>    "payment_source_token": "your-stored-instrument-token",
</strong><strong>    "customer_token": "your-customer-token"
</strong>  }
}
</code></pre>

{% hint style="info" %}
If `payment_source_token` is missing, the session is paid all the same but no wallet is created. These keys are only read from the authorization response — returning them on capture, void, or refund has no effect.
{% endhint %}

{% hint style="warning" %}
Vaulting during a payment requires a `token_url` to be configured — without one, a session asking for `vaulting` is rejected with a `422 Unprocessable Entity` error.
{% endhint %}

### Duplicate wallets

Commerce Layer deduplicates wallets by the `payment_source_token` your integration returns. When a payment session with vaulting enabled returns a token the customer already holds in an active wallet, the session is linked to that wallet instead of creating a new one.

{% hint style="warning" %}
Creating a wallet directly with a `payment_source_token` that's already stored in one of the customer's active wallets is rejected, with a `422 Unprocessable Entity` error pointing to the existing wallet.
{% endhint %}

{% hint style="info" %}
To avoid duplicate wallets for the same instrument, have your integration return the same `payment_source_token` every time that instrument is stored for the same customer.
{% endhint %}

## Webhook events

Your external endpoint can push asynchronous events to Commerce Layer — send them via `POST` request to the `webhook_endpoint_url` attribute exposed on this resource — to update payment transaction states. Each event must include an `event_type`, a `status`, and a `data` object with the required fields:

<table><thead><tr><th width="180.08984375">Event type</th><th>Required fields</th></tr></thead><tbody><tr><td><strong><code>AUTHORIZATION</code></strong></td><td><code>session_token</code>, <code>transaction_token</code>, <code>amount_cents</code></td></tr><tr><td><strong><code>CAPTURE</code></strong></td><td><code>session_token</code>, <code>transaction_token</code>, <code>amount_cents</code>, <code>reference_transaction_token</code></td></tr><tr><td><strong><code>VOID</code></strong></td><td><code>session_token</code>, <code>transaction_token</code>, <code>reference_transaction_token</code></td></tr><tr><td><strong><code>REFUND</code></strong></td><td><code>session_token</code>, <code>transaction_token</code>, <code>amount_cents</code>, <code>reference_transaction_token</code>, <code>authorization_token</code></td></tr></tbody></table>

The `status` field must be one of:

* `requires_action`
* `processing`
* `succeeded`
* `declined`
* `failed`
* `canceled`
* `expired`

Events are matched to a payment session via `data.session_token`. If the referenced transaction does not already exist, it is created automatically.

{% hint style="info" %}
As for the outbound requests, all incoming events must be signed with HMAC-SHA256 using the `shared_secret` — the signature is expected in the `X-CommerceLayer-Signature` header.
{% endhint %}


---

# 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_setting_externals.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.
