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

# Recurring payments

Primer supports merchant-initiated payments (MITs) for recurring and subscription business models. You can use Primer's Payments API to create these payments.

> [!NOTE]
>
> Working out which `paymentType` to send? **One-off payments** have no dedicated payment type. See [Choose the right payment type](#choose-the-right-payment-type).

## Before you start

Before you start, make sure you are ready to [process a payment](https://primer.io/docs/get-started/connect-a-processor.md).

## Generate an API key

Ensure you have [an API key configured](https://primer.io/docs/api-reference/get-started/authentication.md). Make sure to set the following scopes for your API Key:

* `client_tokens:write`
* `transactions:authorize`

## Get a payment method token

The Payments API requires a [payment method token](https://primer.io/docs/concepts/payment-method-token.md#get-a-payment-method-token) to create a payment, so make sure you [save the payment method](https://primer.io/docs/payment-services/save-payment-methods.md) on the first payment so you have a vaulted token for MITs.

## Choose the right token type

Primer uses two token types, one for each point in the payment lifecycle:

* A **single-use token** is generated by Universal Checkout each time a customer enters their details. It's short-lived and authorizes one payment — use it for the customer's **first** payment. See [Payment method token](https://primer.io/docs/concepts/payment-method-token.md).
* A **multi-use token** (also called a *vaulted token*) represents a payment method stored in [Primer's Vault](https://primer.io/docs/payment-services/save-payment-methods.md). It's long-lived and reusable — use it for **subsequent** merchant-initiated payments.

> [!NOTE]
>
> You don't have to vault a card yourself before the first payment. Pass a single-use token with `paymentMethod.vaultOnSuccess` (or `vaultOn3DS`) set to `true`, and Primer vaults the card for you as part of that first payment, returning a multi-use token you can reuse later. See [Save payment methods](https://primer.io/docs/payment-services/save-payment-methods.md).

### Valid combinations

Match the token type to the [payment type](#payment-types) you are sending:

| Payment type | Initiation | Token type to use |
| - | - | - |
| Omitted (one-off) | Customer-initiated (CIT) | Single-use token |
| `FIRST_PAYMENT` | Customer-initiated (CIT) | Single-use token |
| `ECOMMERCE` | Customer-initiated (CIT) | Single-use token for a one-off, multi-use (vaulted) token for a saved payment method |
| `SUBSCRIPTION` | Merchant-initiated (MIT) | Multi-use (vaulted) token |
| `UNSCHEDULED` | Merchant-initiated (MIT) | Multi-use (vaulted) token |

> [!WARNING]
>
> Always use a **single-use token** for `FIRST_PAYMENT`.
>
> `FIRST_PAYMENT` tells Primer to store the credential and capture the scheme data (such as the `Network Transaction ID`) that optimizes every later payment. Sending it with an already-vaulted token is contradictory — the credential is already stored — and prevents Primer from applying the correct stored-credential logic, which can cause processing issues. To store a card during the first payment, use a single-use token with `vaultOnSuccess` instead of vaulting up front.

### Recommended flow

The correct end-to-end flow for storing a card on the first payment and reusing it later:

1. **First payment** — capture the card with Universal Checkout to get a **single-use token**, then create the payment with `paymentType: FIRST_PAYMENT` and `vaultOnSuccess: true` (optionally set [`firstPaymentReason`](#payment-types) to match your intent — `Recurring`, `Unscheduled`, or `CardOnFile`). Primer authorizes the payment and, on success, vaults the card and returns a **multi-use token**.
2. **Subsequent payments** — reuse the **multi-use token** for later merchant-initiated payments, sending `paymentType: SUBSCRIPTION` (fixed schedule and amount) or `UNSCHEDULED` (no fixed schedule or amount).

```bash theme={"dark"}
# 1. First payment — single-use token, vault on success
curl --location --request \
    POST 'https://api.sandbox.primer.io/payments' \
    --header 'X-Api-Key: <YOUR_API_KEY>' \
    --header 'X-Api-Version: 2.4' \
    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \
    --data '{
        "orderId": "<YOUR_ORDER_ID>",
        "amount": 800,
        "currencyCode": "GBP",
        "paymentMethodToken": "<SINGLE_USE_TOKEN>",
        "paymentMethod": {
            "paymentType": "FIRST_PAYMENT",
            "firstPaymentReason": "Recurring",
            "vaultOnSuccess": true
        }
    }'
```

```bash theme={"dark"}
# 2. Subsequent payment — reuse the multi-use (vaulted) token
curl --location --request \
    POST 'https://api.sandbox.primer.io/payments' \
    --header 'X-Api-Key: <YOUR_API_KEY>' \
    --header 'X-Api-Version: 2.4' \
    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \
    --data '{
        "orderId": "<YOUR_ORDER_ID>",
        "amount": 800,
        "currencyCode": "GBP",
        "paymentMethodToken": "<MULTI_USE_TOKEN>",
        "paymentMethod": {
            "paymentType": "SUBSCRIPTION"
        }
    }'
```

## Send a payment request

> [!NOTE]
>
> See our [API Reference](https://primer.io/docs/api-reference/v2.4/api-reference/payments-api/create-a-payment.md) for more details

Below is a minimal payment request example:

```bash theme={"dark"}
curl --location --request \
    POST 'https://api.sandbox.primer.io/payments' \
    --header 'X-Api-Key: <YOUR_API_KEY>' \
    --header 'X-Api-Version: 2.4' \
    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \
    --data '{
        "orderId": "<YOUR_ORDER_ID>",
        "amount": 800,
        "currencyCode": "GBP",
        "paymentMethodToken": "<PAYMENT_METHOD_TOKEN>",
    }'
```

> [!NOTE]
>
> See [Optimize for recurring payments](#optimize-for-recurring-payments) below for more information

When you create a payment, it will start your workflow with a ["Payment created" trigger](https://primer.io/docs/workflows/apps/native-apps/primer-payments-app/payment-created-trigger.md). Learn more about processing payments [here](https://primer.io/docs/get-started/connect-a-processor.md).

Any change in the payment's status (e.g. once it is AUTHORIZED) will trigger a `PAYMENT.STATUS` webhook notification event. Learn more about webhooks [here](https://primer.io/docs/api-reference/get-started/configure-webhooks.md).

### Recover declined or failed payments

A declined transaction doesn't necessarily have to mean the end of the road for your payment.

When authorization is declined by the card issuer or processor gateway, a [`statusReason`](https://primer.io/docs/api-reference/v2.4/api-reference/payments-api/create-a-payment#response-status-reason) object is returned in response to your payment request, providing a decline `code` and processor `message` (if available) with further information that can help you recover the payment. You can also see this information on the payment's [`Timeline`](https://primer.io/docs/concepts/payment-timeline) in the Dashboard.

* **Hard declines** include reasons like invalid information, a closed account, or because the card owner reported the card lost or stolen. These cannot be retried with the existing `paymentMethodToken` because the payment can never be successfully authorized unless the payment method data is changed. (Your customer's expired card, for example, will never *un*expire.)
* **Soft declines** indicate that authorization wasn't possible due to conditions that in some cases may only be temporary, like when the card limit has been exceeded or the issuer is unavailable. It may be possible to complete these payments successfully at a later time with the same information captured in the initial attempt. In these cases, you can use the same `paymentMethodToken` to attempt authorization again.

Issues like latency or service disruption at the processor's gateway are usually the cause of **FAILED** payments, but these can also occur when a configuration error prevents the request from being processed. These transactions may also be successful on a later attempt in the event that there was a technical issue, but you should make sure everything is configured correctly before you retry the payment.

### Handle payment errors

Set up [Webhooks](https://primer.io/docs/api-reference/get-started/configure-webhooks.md) to receive information about declined and failed payments and consume the data contained in the `statusReason` object to handle next steps, or consider setting up a fallback or retry scenario in Workflows.

Information about HTTP error responses (`4XX`) when making an API call to Primer can be found in the [Errors](https://primer.io/docs/api-reference/get-started/api-responses.md#error-responses) section of the [API Reference](https://primer.io/docs/api-reference/get-started/overview).

## Optimize for recurring payments

Payments are tied together through the `Network Transaction ID`, the ID assigned by card networks to an initial payment in a series of payments. Subsequent payments can then be routed to any processor, rather than being tied to the processor used for the initial authorization.

For Mastercard, Primer also captures and sends the Transaction Link ID (TLID) on supported processors. See [Mastercard Transaction Link ID (TLID)](#mastercard-transaction-link-id-tlid) below.

While the `Network Transaction ID` is a universal value, every [processor](https://primer.io/docs/get-started/connect-a-processor.md) has additional, unique requirements around what data is sent for payments made using a stored payment method. Normally you would need to calculate these requirements yourself on a processor by processor basis, but at Primer we handle these fields for you.

Let's take a common example - processing subscriptions, using three different processors. For various reasons, including fee and authorization rate optimization, a customer's card could be against any one of those processors throughout the life of their subscription. But each processor has different requirements:

| | Code Requirement | Context |
| - | - | - |
| **Processor 1** | - *merchant\_initiated: recurring*  -  *first\_stored: 01/01/1970* | - The type of merchant initiated transaction  - Where the card was first stored. |
| **Processor 2** | - *payment\_type: subscription*  -  *customer\_present: false*  - *externally\_stored: true* | - The type of merchant initiated transaction   - If the customer is present  - Whether the card was stored with the processor or elsewhere |
| **Processor 3** | - *charge\_intent: subscription* | - The type of merchant initiated transaction |

Managing these differing needs adds considerable complexity to the already elaborate task of running a subscription business; and that's without considering all of the other intricacies managing multiple processors can bring.

**Primer therefore manages this complexity for you.** We store the mapping for each processor and calculate the correct fields to send for each transaction. Our internal logic reviews multiple inputs, including if the customer is present at the point of making the payment, whether the payment method has been stored before and what type of payment method is being used.

Importantly, we also take into account the **payment type** you pass in your payment request. While the parameter isn't mandatory, we recommend including it - both for your own reporting and to ensure your intent for the payment is aligned with the fields we calculate.

Using this wealth of information, Primer can automatically calculate the correct fields to send to any processor. During this process we also store and handle the related acquirer and scheme data in order to increase approvals.

> [!WARNING]
>
> Note: this logic is present in API v2.4 onwards. We recommend [upgrading to API v2.4](https://primer.io/docs/changelogs/migration-guides/API-2.3-to-2.4.md) as soon as possible and in the interim, always passing the `paymentType` parameter when processing recurring payments with API ≤v2.3.

### Mastercard Transaction Link ID (TLID)

The Transaction Link ID (TLID) is a unique identifier that Mastercard generates for a customer-initiated transaction (CIT). Mastercard requires every later merchant-initiated transaction (MIT) in the same series, such as a subscription renewal or an unscheduled charge, to carry the TLID from the original CIT. The TLID works alongside the `Network Transaction ID`. It does not replace it, and Primer sends both.

| Date | What happens |
| - | - |
| 23 October 2026 | Every Mastercard MIT must carry the TLID from the original CIT. |
| 1 December 2026 | Comply-by date. Some acquirers communicate slightly different dates. |
| 31 January 2027 | Mastercard starts assessing non-compliance fees. |

#### How Primer handles the TLID

* When a payment saves a card to Primer's vault, Primer stores the TLID returned by the processor on the saved card.
* On every MIT made with that card, Primer sends the stored TLID to the processor.
* Cards saved before TLID support pick up a TLID on their next payment. Every MIT after that carries it.

This happens automatically. You don't need to change your integration.

If your processor stores the card itself (processor-native tokens), the processor handles the TLID. Check with your processor if you have questions.

#### Processor coverage

* **Live:** Adyen, Checkout.com, Cybersource, Nuvei, Stripe, and processors connected through [Primer for Partners](https://primer.io/docs/connections/primer-for-partners/overview.md).
* **Coming soon:** Worldpay, once Worldpay releases TLID on its side. For Worldpay, ask your Worldpay account manager to enable TLID for your merchant codes.
* **Other processors:** Primer adds support as each processor releases TLID on its side.

If your processor has confirmed it supports TLID and it isn't listed here, contact your account manager and we'll make sure it's supported.

### Payment Types

#### Choose the right payment type

Use the table below to pick the values to send for each scenario.

| Your scenario | `paymentType` | Other fields to send | Token type |
| - | - | - | - |
| **One-off payment**: the customer pays once and you are not storing the payment method | Omit it, or send `ECOMMERCE` if you prefer to be explicit | `vaultOnSuccess` and `vaultOn3DS` left unset or `false` | Single-use |
| **First payment, storing the card for future customer-initiated payments** ("card-on-file") | `FIRST_PAYMENT` | `vaultOnSuccess: true`, `firstPaymentReason: CardOnFile` | Single-use |
| **First payment, storing the card for future merchant-initiated payments** | `FIRST_PAYMENT` | `vaultOnSuccess: true`, `firstPaymentReason: Recurring` (fixed schedule) or `Unscheduled` | Single-use |
| **Customer pays with a payment method they saved earlier** | `ECOMMERCE` | None | Multi-use (vaulted) |
| **Merchant-initiated payment on a fixed schedule and set amount** | `SUBSCRIPTION` | None | Multi-use (vaulted) |
| **Merchant-initiated payment with no fixed schedule or amount** | `UNSCHEDULED` | None | Multi-use (vaulted) |

> [!NOTE]
>
> **The Payments API has no `ONE_OFF` payment type.** For a one-off payment, leave `paymentType` unset; on v2.4, Primer works out the correct one-off fields for the processor. `ECOMMERCE` behaves the same, and makes your intent explicit on the payment.

> [!TIP]
>
> Prefer omitting `paymentType`, or sending `ECOMMERCE`, over `FIRST_PAYMENT` for a one-off payment.
>
> On v2.4, all three resolve to a one-off as long as `vaultOnSuccess` and `vaultOn3DS` are false. Existing integrations sending `FIRST_PAYMENT` for one-off payments keep working and need no migration. But `FIRST_PAYMENT` states that further payments will follow on the same credential, so it stops being accurate the moment you add a save-card option to the flow: the payment is then treated as the first of a series at the processor. Omitting `paymentType` or sending `ECOMMERCE` stays correct either way.

#### Additional parameters

**paymentMethod -> paymentType**

* *Omitted*: a one-off customer-initiated payment.
* `FIRST_PAYMENT`: a customer-initiated payment that is the first in a series, such as subscriptions, unscheduled transactions, or "card-on-file" scenarios.
* `ECOMMERCE`: Customer-initiated payment where the cardholder is present, either as a one-off or using a stored payment method.
* `SUBSCRIPTION`: Merchant-initiated payment as part of a series of payments on a fixed schedule and for a set amount.
* `UNSCHEDULED`: Merchant-initiated payment using stored payment details with no fixed schedule or amount.

If you are storing a payment method for the first time, it is also recommended you pass the relevant `paymentMethod.firstPaymentReason` to indicate why the payment method is being stored.

**paymentMethod -> firstPaymentReason**

* `CardOnFile`: For "card-on-file" scenarios, correlating with the ECOMMERCE paymentType.
* `Recurring`: For future subscription payments, correlating with the SUBSCRIPTION paymentType.
* `Unscheduled`: For future unscheduled payments, correlating with the UNSCHEDULED paymentType.

This field is particularly relevant for processors that rely on precise card storage logic. We recommend setting the correct value when storing cards for a specific purpose, as this ensures compliance with processor requirements and optimizes performance.

*Note: Many processors benefit from the `firstPaymentReason` field for enhanced payment flows and compliance. While the implementation is complete for several key processors (e.g., Adyen, Cybersource, MPGS, Checkout), we continue to expand support to provide a seamless experience across additional platforms.*

## Avoid duplicated payments

Primer supports a request idempotency mechanism for our Payments API. This optional feature enables you to safely retry a request without risking the user being charged or refunded multiple times.

To make an idempotent request, generate an idempotency key and pass it to the header `X-Idempotency-Key`.

```bash theme={"dark"}
curl -X POST 'https://api.primer.io/<ENDPOINT>' \
  --header 'X-Idempotency-Key: <idempotency-key>'
```

Example idempotency error response:

```json JSON theme={"dark"}
{
	"error": {
		"errorId": "TransactionRequestIdempotencyKeyAlreadyExists",
		"description": "Transaction request already exists with idempotency key 'IDEMPOTENCY_KEY'.",
		"diagnosticsId": "3233041960986039254",
		"validationErrors": [],
		"paymentId": "6gHJtooK6",
		"paymentStatus": "SETTLED"
	}
}
```

> [!NOTE]
>
> `paymentId` and `paymentStatus` are only available from API version 2.2 onwards

If an API call fails due to the request being invalid, due to a network issue, or if Primer is momentarily unavailable, make another request with the same idempotency key:

* If a request with the same idempotency key has already been successfully processed by Primer, the new request will be ignored. A `400` error will be returned with an `errorId` set to `TransactionRequestIdempotencyKeyAlreadyExists`.
* On API version `2.2` and later, we will include the `paymentId` and `paymentStatus` in the error response to provide more insights into the related payment. This data can be used to validate which payment was already created in case of a duplicate request.
* Otherwise, Primer will attempt to process the new request.

> [!NOTE]
>
> The way you generate the idempotency key is totally up to you, as long as it is unique per request attempt.


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