Headless Universal Checkout is currently in beta for Web, iOS, Android and React Native. The following payment methods are unsupported:


Web currently only supports PAYMENT_CARD.

Where there is a need for more customization and control over the checkout experience, a headless version of Primer’s Universal Checkout is available. You can use Headless Universal Checkout with your own UI, giving you more flexibility and allowing you to move faster when making design changes, while still having Universal Checkout capture sensitive PCI card data or other form data.

As with all versions of Universal Checkout, any payment method can be added and configured through the Primer Dashboard, meaning Primer handles the logic of when to display each method.

Primer Headless Universal Checkout works in a simple way:

  1. 1
    Get a clientToken from your server
  2. 2
    Start Primer Headless Universal Checkout with the client token
  3. 3
    Primer Headless Universal Checkout will then return the available payment methods for the session initiated. Those payment methods that have been configured in the Dashboard and whose conditions match the current client session will be returned.
  4. 4
    Show the list of available payment methods.
  5. 5
    When the user selects a payment method, show its UI to enable the user to enter their credentials. Depending on the payment method, you will have to either ask the SDK to render it, or build the UI yourself.
  6. 6
    Primer Headless Universal Checkout will then create a payment for you and manage its lifecycle. You will receive a confirmation of payment with a callback to indicate the checkout flow has completed.

Headless Universal Checkout is currently available in beta on React Native. This documentation is only relevant for v2.5.0 and upward. The documentation for version v2.4.0 is available here.


Step 1. Prepare the Headless Universal Checkout callbacks

Set the Headless Universal Checkout callbacks to handle what happens during the checkout’s lifecycle.

const onAvailablePaymentMethodsLoad = (paymentMethodTypes: string[]) => {    // paymentMethodTypes contains the payment methods identifiers available    // for the client session.} const onPrepareStart = (paymentMethodType: string) => {    // Payment methods is getting prepared.} const onPaymentMethodShow = (paymentMethodType: string) => {    // Payment method did get presented.} const onTokenizeStart = (paymentMethodType: string) => {    // Started tokenization of payment method.} const onCheckoutComplete = (checkoutData: PrimerCheckoutData) => {    // It will receive the payment when it's done} const settings: PrimerSettings = {    // Any other settings & callbacks (see the Quick Start Guide    // and the SDK API reference)     onAvailablePaymentMethodsLoad: onAvailablePaymentMethodsLoad,    onPrepareStart: onPrepareStart,    onPaymentMethodShow: onPaymentMethodShow,    onTokenizeStart: onTokenizeStart,    onCheckoutComplete: onCheckoutComplete,}

Step 2. Start Headless Universal Checkout

Once you have a client token, you can initialize Primer’s Headless Checkout like below, and wait to receive the available payment method types for this client session:

try {    const paymentMethodTypes = await HeadlessUniversalCheckout.startWithClientToken(session.clientToken, settings)    setPaymentMethodTypes(paymentMethodTypes)     // Process payment methods} catch (e) {    // Handle error}

Once Primer’s Headless Checkout is configured, its callbacks will notify you about Headless Universal Checkout events.


Check the SDK API here to customize your SDK settings.


Based on the values in the Client Session (e.g. currencyCode or countryCode) you can display different payment methods in the checkout. Look at the “Checkout” tab on the Primer Dashboard to configure the rules for payment method display.

Step 3: Show available payment method

When the checkout is done initializing and you have received the available payment methods, you can show the list of payment methods to the user.

You can get the assets for each payment method using the function getAssetForPaymentMethodType.

// Get the logos of the payment methods available for this sessionconst paymentMethodsImageUrls = (    await Promise.all(        paymentMethodTypes.map(paymentMethodType =>            HeadlessUniversalCheckout.getAssetForPaymentMethodType(paymentMethodType, 'logo'),        ),    )).reduce((acc, logo, index) => {    acc[paymentMethodTypes[index]] = logo    return acc}, {}) // paymentMethodsImageUrls[paymentMethodType] contains the logo for the payment method

Assuming you have stored the logo URLs in paymentMethodsImageUrls, you can now render the available payment methods.

const renderPaymentMethods = () => {    return (        <View>            {paymentMethodTypes.map(paymentMethodType => {                return (                    <TouchableOpacity                        key={paymentMethodType}                        onPress={() => {                            payWithPaymentMethod(paymentMethodType)                        }}                    >                        <Image source={{ uri: paymentMethodsImageUrls[paymentMethodType] }} resizeMode='contain' />                    </TouchableOpacity>                )            })}        </View>    )}

Step 4: Handle payment method selection

When the user selects a payment method, you should show the payment method to the customer, either by asking the SDK to show it, or by building the UI.

Headless Universal Checkout currently handles payment methods in two ways:

  • With the function showPaymentMethod that renders a view all handled by Primer. This is the case today for redirect-based payment methods and Apple Pay or Google Pay.
  • With a “Raw Data Manager” that provides you with the list of data to capture so that you can build your on UI. This is the case today for card payments.

Step 4.1: Handle showPaymentMethod

Use the function showPaymentMethod to display a payment method that’s handled by Primer.

const payWithPaymentMethod = (paymentMethodType: string) => {    try {        await HeadlessUniversalCheckout.showPaymentMethod(paymentMethodType)    } catch (err) {        // Handle the error    }}

The user can now interact with Universal Checkout, and the SDK will create the payment. The payment’s data will be returned on onCheckoutComplete configured in Step 1.

Step 4.2: Show card form

Headless Universal Checkout securely captures payment method data while fully embedded in your app. By communicating directly with Primer's PCI-L1 tokenization service, Universal Checkout transforms sensitive customer data into a secure uniform string called a payment method token. Universal Checkout automatically uses this token to create a payment.

Headless Universal Checkout on React Native does not support card components at the moment.

If the user wants to pay with a card, you can display a card form for paying by card.

Primer will provide you with the required inputs. You should use them to construct your UI. With the raw (card) manager, you can use your fully customized UI and still use all of the features that make Primer great.

At any point, you can validate your data by listening to the onValidation callback. Primer will return errors specific to each required input.

When the data is valid, you can send it to Primer for further processing by calling submit.

Configure the HeadlessUniversalCheckoutRawDataManager and subscribe to callbacks

First, configure the HeadlessUniversalCheckoutRawDataManager, and optionally set the onValidation and the onMetadataChange callbacks.

Listen to the card data validation state and enable your form’s submit button accordingly.

const options: PrimerHeadlessUniversalCheckoutRawDataManagerOptions = {    paymentMethodType: 'PAYMENT_CARD',    onMetadataChange: data => {        const cardNetwork = data.cardNetwork        // Show a card network icon    },    onValidation: (isValid, errors) => {        // Modify your UI        submitButton.isEnabled = isValid    },} await HeadlessUniversalCheckoutRawDataManager.configure(options)

Render input elements

First, use the HeadlessUniversalCheckoutRawDataManager object and render the card form input elements like this:

const requiredInputElementTypes: string[] = await HeadlessUniversalCheckoutRawDataManager.getRequiredInputElementTypesForPaymentMethodType(    'PAYMENT_CARD',)

getRequiredInputElementTypesForPaymentMethodType will return a string array.



Primer Headless Universal Checkout requires input data to be sent for further processing. For PAYMENT_CARD, you should pass a PrimerRawCardData object:

const cardData: PrimerRawCardData = {    cardNumber: '4242424242424242',    expiryMonth: '12',    expiryYear: '2024',    cvv: '123',    cardholderName: 'John Smith',}

Submit input data changes

In order to validate input data, and do further processing, submit input data changes:

await HeadlessUniversalCheckoutRawDataManager.setRawData(cardData) // The onValidation and onMetadataChange will be called notifying you// about the card data validation.

Submit input data

Finally, submit the validated input data:

await HeadlessUniversalCheckoutRawDataManager.submit()

When the function submit is called, the SDK will attempt to create a payment. The payment’s data will be returned on onCheckoutComplete configured in Step 1.

Step 5. Handle Listener Callbacks (Optional)

Handle onPrepareStart Callback (Optional)

This function will notify you that the tokenization preparation has started. At this point the SDK has not yet started to communicate with Primer's backend. It may be useful in case you want to show a loader or notify user about progress in any other way.

const onPrepareStart = (paymentMethodType: string) => {    // Payment method is getting prepared.}

Handle onTokenizeStart Callback (Optional)

This function will notify you that the tokenization API call has been fired. It may be useful in case you want to show a loader or notify user about progress in any other way.

const onTokenizeStart = (paymentMethodType: string) => {    // Payment method is getting prepared.}

Handle onPaymentMethodShow Callback (Optional)

This function will notify you that the payment method you requested to show has been presented.

const onPaymentMethodShow = (paymentMethodType: string) => {    // Update your UI}

Handle errors

Use the onError callback of the HeadlessUniversalCheckout options to handle any errors emitted by the SDK during the checkout flow.

const onError = (    error: PrimerError,    checkoutData: PrimerCheckoutData | null,    handler: PrimerErrorHandler | undefined,) => {    // Handle the error}

Prepare 3DS

  • In order to make the SDK lightweight, 3DS requires the addition of the Primer 3DS library. Check out our guide on how to add the Primer 3DS library to your React Native app.
  • When the user pays by card, the Workflow will decide whether or not a 3DS challenge is required. If so, Headless Universal Checkout will automatically render the 3DS challenge.
  • To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:
  1. customer
The customer's email address
  1. customer
The customer's billing address

Learn more about how to configure 3DS!

Did It Work?

If everything went well, you should be able to see the payment you just created on your Dashboard under the Payments menu. Payment list