Universal Checkout creates payments for you automatically. However, you can also handle payment creation manually.

This guide shows how to manually create payments, both through Universal Checkout and also through Payments API for recurring payments.


We recommend to get yourself familiar with Primer Payments and Payment method tokenization before reading this guide.

Manually create payments

1. Create a payment request

Use the Payment Method Token from Universal Checkout (see Manual Payment Creation) or the Payment Methods API to create a payment:

payment creation

Payment request POST/payments

curl --location --request \    POST 'https://api.sandbox.primer.io/payments' \    --header 'X-Api-Key: <YOUR_API_KEY>' \    --header 'X-Api-Version: 2.2' \    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \    --data '{        "orderId": "<YOUR_ORDER_ID>",        "amount": 800,        "currencyCode": "GBP",        "paymentMethodToken": "<PAYMENT_METHOD_TOKEN>",    }'
Required fields
your unique order ID

Payment amount in minor units

  • example in currency (USD) with minor units: 700 for \$7.00
  • example in currency (JPY) without minor units: 100 for ¥100

ISO 4217 three-letter currency code: USD for US dollars

paymentMethodToken obtained from Universal Checkout or the Payment Methods API


Data passed in the payment request overwrites any data passed in the Client Session used to initialize Universal Checkout.

See more request options in the API Reference.

Payment response

The payment request is synchronous. It will trigger a Workflow and return a payment object containing, among other things, the payment


In circumstances where the payment flow is asynchronous, you may receive a status of .

// 2XX response {    "id": "slklmjh4",    "status": "AUTHORIZED",    "orderId": "<YOUR_ORDER_ID>",    "currencyCode": "GBP",    "amount": 800,    "paymentMethodToken": "<PAYMENT_METHOD_TOKEN>"     // ... additional attributes for the Payment object    // ... see API ref}

2. Resuming payment flows

Payment response with requiredAction 👇

Some Connections, such as 3D Secure, can pause the Workflow pending further action. When this happens, the requiredAction object returned in the response to your payment request will include a new clientToken. Return this to Universal Checkout to render next steps and continue.

// 2XX response {    "id": "slklmjh4",    "status": "PENDING",    "orderId": "<YOUR_ORDER_ID>",    "currencyCode": "GBP",    "amount": 800,    "paymentMethodToken": "<PAYMENT_METHOD_TOKEN>",     "requiredAction": {        "name": "3DS_AUTHENTICATION",        "description": "A 3DS Authentication is required. Return clientToken.",        "clientToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."    }     // ... additional attributes for the Payment object    // ... see API ref}

Resume payment request POST/payments/{paymentId}/resume

Once the required action is completed, Universal Checkout will return a resumeToken. Use this to resume the Workflow for the payment. You will receive an updated payment status in response.

curl --location --request \    POST 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>/resume' \    --header 'X-Api-Key: <YOUR_API_KEY>' \    --header 'X-Api-Version: 2.2' \    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \    --data '{        "resumeToken": "9QpvyT3qNFh6CbJSa0HwxNjE4MjI0NTYw"    }'

When resuming a payment, another requiredAction may be returned in the response, which restarts the cycle until all required actions in the Workflow are complete.

3. Handle pending payments with Webhooks

When creating payments, you may receive a payment status of . This means that the payment is awaiting another step (such as a required action in Workflows, or a callback for asynchronous processors) in order to advance to the next step or Connection. Set up Webhooks to be notified when the payment status changes.


Create recurring payments

Primer supports merchant initiated payments for repeat customers, recurring and subscription business models.

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

To optimize authorization rates for recurring payments, pass

in the payment request. Primer automatically stores and handles processor, acquirer and scheme data in order to increase approvals.

Additional parameters
  1. paymentMethod
  • FIRST_PAYMENT: a customer-initiated payment which is the first in a series of recurring payments for subscriptions, or "card-on-file" scenarios. This field is automatically set when vaulting cards for the first time.
  • ECOMMERCE: a customer-initiated payment using a stored payment method where the cardholder is present
  • SUBSCRIPTION: a merchant-initiated payment as part of a series of payments on a fixed schedule and for a set amount
  • UNSCHEDULED: a merchant-initiated payment using stored payment details with no fixed schedule or amount

Handle payment lifecycle


All Primer payments conform to a unified payment lifeycle.

Each payment will progress through some combination of the Payment Statuses listed below on their way through your Workflow.

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

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 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 a service disruption at the processor's gateway are usually the cause of payments, but these can also occur when a configuration error prevented 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 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 section of the API Reference.

Cancel payments

Cancel request POST/payments/{paymentId}/cancel

Provided the payment has not reached status, Primer will send a void request to the processor, thereby canceling the payment and releasing the hold on customer funds. Upon success, the payment status will be updated to .

curl --location --request \    POST 'https://api.sandbox.primer.io/payments/<PAYMENT_ID>/cancel' \    --header 'X-Api-Key: <YOUR_API_KEY>' \    --header 'X-Api-Version: 2.2' \    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>'

Refund payments

Refund request POST/payments/{paymentId}/refund

By default, this will refund the full amount against the processor. Optionally, pass in a lesser

for partial refunds, if supported by the processor.

curl --location --request \    POST 'https://api.sandbox.primer.io/payments/<PAYMENT_ID>/refund' \    --header 'X-Api-Key: <YOUR_API_KEY>' \    --header 'X-Api-Version: 2.2' \    --header 'X-Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>' \    --data '{        "amount": 200, # supply this parameter for a partial refund, otherwise defaults to full amount        "orderId": "order-123",    }'