Sandbox
Help

Accept payments via Click to Pay - Web (Beta)

Beta

The Click to Pay SDK is currently in Beta. To enable it for your merchant account, contact your Revolut account executive.

Revolut's Click to Pay widget allows you to offer your customers a faster, more secure checkout experience backed by Visa and Mastercard.

Enrolled customers can complete payments in just a few clicks - the card networks handle authentication and payment credential delivery behind the scenes. Customers can enter their card details during checkout through Click to Pay and opt in to save them to a new or existing Click to Pay profile for future purchases.

In this tutorial, we'll guide you through integrating Click to Pay, powered by the Revolut Checkout Widget. Customers can authenticate with their card network and complete the payment within a pop-up on your checkout page, reducing friction and improving conversion rates.

Click to Pay on Web

Limitations
  • One-time payments only: Click to Pay supports one-time card-not-present payments. Merchant-initiated transactions (MIT) and recurring payments are not available. For subscriptions and recurring billing, consider SEPA Direct Debit or Revolut Pay.
  • Country availability: Click to Pay is available in 63 countries. See the full country list for details.
  • No sandbox: Click to Pay is not available in the sandbox environment. Testing requires a production merchant account.

What is Click to Pay

Click to Pay is an EMVCo Secure Remote Commerce (SRC) standard backed by Visa and Mastercard. It enables customers to pay online with a streamlined, card-network-backed checkout experience. For enrolled customers, the card networks securely store payment credentials and handle authentication - no manual card entry required. Customers can enter their card details during checkout through Click to Pay and opt in to save them to a new or existing Click to Pay profile for future use.

Click to Pay supports three consumer journeys:

  • Recognised consumer: the customer's device is already recognised by the card network, and their Click to Pay profile is loaded automatically - no OTP or manual entry required.
  • Returning but unrecognised consumer: the customer's device is not recognised, the customer enters their email, verifies their identity with an OTP sent by the card network, and selects from their saved cards.
  • New consumer: the customer chooses Click to Pay, enters their card details and profile information, and can opt in to save their card to a new Click to Pay profile for future use.

In all cases, 3DS authentication is handled by the card network within the Click to Pay flow. Revolut receives the authenticated payment credentials and performs authorisation, just like a standard card payment.

How it works

Implementing the Click to Pay widget involves these core components:

  1. Server-side: expose an endpoint on your backend that creates an order using the Merchant API: Create an order endpoint.
  2. Client-side: the Click to Pay widget opens a pop-up where the customer authenticates with their card network and completes the payment, using your endpoint to create the order in the background.
  3. Webhook endpoint: set up webhooks to monitor payment lifecycle events. Click to Pay uses the same standard card payment webhook events as regular card payments.

The order and payment flow is as follows:

  1. The customer goes to the checkout page, chooses to pay via Click to Pay and clicks the payment button.
  2. The widget opens the Click to Pay pop-up and calls your createOrder function to create the order via your backend in the background.
  3. The customer authenticates with their card network (Visa or Mastercard) within the pop-up.
  4. The card network performs 3DS authentication and securely delivers the payment credentials to Revolut.
  5. Revolut authorises the payment, just like a standard card payment.
  6. The onSuccess callback fires - the payment has been authorised.
  7. Your server receives the ORDER_COMPLETED webhook event (or ORDER_PAYMENT_DECLINED / ORDER_PAYMENT_FAILED / ORDER_CANCELLED if unsuccessful).

For more information about the order and payment lifecycle, see: Order and payment lifecycle.

Implementation overview

Here is an overview of the key integration steps:

  1. Set up an endpoint for creating orders
  2. Install the SDK
  3. Initialise the SDK
  4. Configure and mount the widget
  5. Handle payment results

Before you begin

Before you start, make sure you have the following:

  • An active Merchant account with Revolut Business. If you don't have one yet, apply for a Merchant account.
  • Your API keys - both your public key (for initialising the SDK) and secret key (for creating orders on your backend). See Generate the API keys.
  • A backend server to securely create orders using the Merchant API. Your secret API key must never be exposed on the client side.
  • A website checkout page where you'll mount the Click to Pay widget.

Implement Click to Pay

The following steps walk you through the full integration: setting up your backend to create orders, installing and initialising the SDK, configuring and mounting the Click to Pay widget, and handling payment results.

1. Set up an endpoint for creating orders

Before implementing the client-side widget, you must first create a dedicated endpoint on your server. This is a critical security step, as your secret API key must never be exposed on the client side.

The role of this server-side endpoint is to act as a secure bridge between your frontend and the Merchant API. When a customer initiates a payment on your website, your frontend will call this endpoint. Your endpoint is then responsible for:

  1. Receiving the checkout details (e.g., amount, currency) from the frontend request.
  2. Securely calling the Merchant API: Create an order endpoint with the received details.
  3. Receiving the order details from the Merchant API, including the public token.
  4. Passing this token back to your frontend in the response.

Later, in the client-side SDK configuration, the createOrder callback function will call this endpoint to fetch the token, which is required to initialise the checkout widget.

Send a POST request to the Create an order endpoint with the order details, including your Secret API key in the authorisation header:

Request example
POST /api/orders HTTP/1.1
Host: merchant.revolut.com
Authorization: Bearer sk_abcdef12347890_...
Content-Type: application/json
Revolut-Api-Version: 2026-04-20

{
  "amount": 1000,
  "currency": "GBP"
}
ParameterDescriptionRequired
amountThe order amount in minor currency unit (e.g., cents). For £10.00, use 1000.Yes
currency3-letter ISO 4217 currency code for the payment. See supported currencies.Yes

Replace Revolut-Api-Version with your target version. We highly recommend using the latest API version to access the latest features.

For complete details on all available parameters, see: Merchant API: Create an order.

2. Install the SDK

Before you begin working with the SDK, ensure the Revolut Checkout Widget is installed in your project. This widget is a necessary component to create and configure the Click to Pay widget. You can install the widget via your project's package manager.

npm install @revolut/checkout
yarn add @revolut/checkout
pnpm add @revolut/checkout
bun add @revolut/checkout

Alternatively, you can add the widget to your code base by adding the embed script to your page directly. To learn more, see: Installation.

3. Initialise the SDK

Import RevolutCheckout from @revolut/checkout in your frontend code, then use the RevolutCheckout.payments() module's clickToPay instance with your Merchant API Public key to initialise the widget.

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

// Configuration code will go here
ParameterDescriptionTypeRequired
publicTokenYour Merchant API public keystringYes
modeAPI environment. Default: 'prod''prod' | 'sandbox'No
localeWidget language (defaults to 'auto' for automatic detection)LocaleNo

For more information about the RevolutCheckout.payments() module, see: Payments module

4. Configure and mount the widget

Prepare your checkout page by adding a button that users click to initiate the payment, then invoke the clickToPay({ ... }) instance with your configuration parameters and call the .show() method to open the Click to Pay pop-up.

4.1 Add a DOM element

First, add a button to your checkout page where you want the widget to appear. This can be a standalone button, or it can be triggered from a payment method selector - for example, the customer selects Click to Pay from a list of payment options, then clicks a pay button that triggers this button's click handler.

checkout.html
<!-- ... -->

<button id="click-to-pay-button">Pay with Click to Pay</button>

<!-- ... -->

4.2 Set up the click handler

Attach a click handler to your button. For now, this is just a placeholder - you'll add the widget configuration in the next step.

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

const clickToPayButton = document.getElementById('click-to-pay-button')

clickToPayButton.addEventListener('click', async () => {
  // Widget configuration will go here
})

4.3 Configure the widget

Invoke the clickToPay({ ... }) instance inside the click handler with your configuration parameters, and call .show() to open the Click to Pay pop-up.

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

const clickToPayButton = document.getElementById('click-to-pay-button')

clickToPayButton.addEventListener('click', async () => {
  const instance = clickToPay({
    createOrder: async () => {
      // Call your backend here to create an order and return order.token
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },
  })

  instance.show()
})
Code snippetDescription
RevolutCheckout.payments(...)Initialises the Revolut Checkout payments module and returns an object containing the clickToPay factory. Pass your publicToken and mode here.
clickToPayCreates a Click to Pay instance when called with your options. It returns a ClickToPayInstance.
ClickToPayInstanceThe instance returned by clickToPay. It exposes .show() to open the Click to Pay pop-up and .destroy() to close the widget and clean up resources.
createOrderA function you define to send order details like amount, currency from your frontend to your backend. Your backend then calls Merchant API: Create an order, receives the token, and returns { publicId: order.token } to the widget, so it can start the checkout session.
Instance methodsCall .show() to open the Click to Pay pop-up. Call .destroy() to close the widget and clean up resources.

For more details about the available parameters, see: Merchant Web SDK: Click to Pay.

Additional settings

The clickToPay module of the Revolut Checkout Widget offers an additional setting you can leverage to enhance user experience.

Provide billing address

Pass the billingAddress parameter to provide the customer's billing address for payment processing. The SDK accepts an Address object.

The SDK does not collect billing address from the customer. You must collect the billing address on your frontend and pass it to the SDK via the billingAddress option.

The SDK uses this data for payment processing and regulatory requirements.

my-app.js
const instance = clickToPay({
  createOrder: async () => {
    const order = await yourServerSideCall()
    return { publicId: order.token }
  },

  // Collect billing address on your frontend and pass it here
  billingAddress: {
    countryCode: 'GB',
    region: 'London',
    city: 'London',
    streetLine1: '1 Revolut Street',
    streetLine2: '',
    postcode: 'EC2V 7HN',
  },
})

instance.show()
Instance lifecycle management

The clickToPay() method returns a ClickToPayInstance with .show() and .destroy() methods. You control when to open and close the widget.

Best practices:

  • Create the instance once when you need it and reuse it across multiple clicks - avoid creating a new instance on every click unless you're destroying the previous one.
  • Call .destroy() when the widget is no longer needed (e.g., page navigation, component unmount) to clean up event listeners and resources.
  • If you create a new instance per click, destroy the previous one first to prevent multiple pop-ups from stacking.
my-app.js
let clickToPayInstance

// Create instance once
const instance = clickToPay({
  createOrder: async () => {
    const order = await yourServerSideCall()
    return { publicId: order.token }
  },
  onSuccess: ({ orderId }) => {
    console.log('Payment successful!', orderId)
    // Destroy after successful payment
    clickToPayInstance.destroy()
    window.location.href = `/confirmation?orderId=${orderId}`
  },
  onError: ({ error }) => {
    console.error('Payment failed:', error.message)
  },
  onCancel: () => {
    console.log('Payment cancelled')
  },
})

clickToPayInstance = instance

// Show on click
document.getElementById('click-to-pay-button').addEventListener('click', () => {
  instance.show()
})

// Clean up when no longer needed (e.g., page unload)
window.addEventListener('beforeunload', () => {
  instance.destroy()
})

5. Handle payment results

Click to Pay payments are processed like standard card payments. The client-side callbacks provide immediate feedback to the customer, while webhooks deliver the final payment outcome to your backend.

Client-side callbacks

The widget is configured and mounted. Add the event callbacks to handle the payment results. Add onSuccess, onError, and onCancel inside the clickToPay({...}) configuration object:

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

const clickToPayButton = document.getElementById('click-to-pay-button')

clickToPayButton.addEventListener('click', async () => {
  const instance = clickToPay({
    createOrder: async () => {
      // Call your backend here to create an order and return order.token
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    onSuccess: ({ orderId }) => {
      // Payment completed successfully
      console.log('Payment successful!', orderId)
      window.location.href = `/confirmation?orderId=${orderId}`
    },

    onError: ({ error, orderId }) => {
      console.error('Payment failed:', error.message, orderId)
      alert(`Payment failed: ${error.message}`)
    },

    onCancel: ({ orderId }) => {
      console.log('Payment cancelled', orderId)
      alert('Payment was cancelled.')
    },
  })

  instance.show()
})
CallbackDescription
onSuccessFires when the payment has been authorised. With automatic capture (default), the payment is captured immediately after. With manual capture, you capture the payment separately. Redirect to a confirmation page or display a success message.
onErrorFires when an error occurs. Receives RevolutCheckoutError. Use it to display error messaging, log diagnostics, or prompt the user to try again.
onCancelFires when the user cancels the payment. orderId may be undefined if order creation failed. Use it to re-enable the checkout form or track abandonment.

With automatic capture (default), authorisation and capture happen in quick succession. If you use manual capture, onSuccess fires after authorisation - you must capture the payment separately to complete the transaction.

Callbacks are not guaranteed

Widget callbacks are not guaranteed to fire due to network issues, browser closures, or ad-blockers. Always use webhooks for critical backend operations like order fulfilment.

Webhooks

Set up your backend to receive webhooks for payment lifecycle events. Click to Pay uses the same standard card payment webhook events as regular card payments - there are no Click-to-Pay-specific webhook events.

Send a POST request to the Create a webhook endpoint with your webhook URL and the events you want to subscribe to:

Request example
POST /api/webhooks HTTP/1.1
Host: merchant.revolut.com
Authorization: Bearer sk_abcdef12347890_...
Content-Type: application/json

{
  "url": "https://example.com/webhooks",
  "events": [
    "ORDER_COMPLETED",
    "ORDER_AUTHORISED",
    "ORDER_PAYMENT_DECLINED",
    "ORDER_PAYMENT_FAILED",
    "ORDER_CANCELLED",
    "ORDER_FAILED"
  ]
}
ParameterDescriptionRequired
urlYour webhook URL. Must be a valid HTTP or HTTPS URL capable of receiving POST requests.Yes
eventsArray of event types to subscribe to. See Webhook events for the full list.Yes

For detailed information on setting up webhooks, see: Use webhooks to track order and payment lifecycle.

Payment outcomes

Handle the following scenarios:

The payment has been authorised by the card network.

Webhook expected:

Webhook eventDescription
ORDER_AUTHORISEDThe payment has been authorised but not yet captured
ORDER_COMPLETEDThe payment has been captured and completed

What to do:

  • Automatic capture (default): both ORDER_AUTHORISED and ORDER_COMPLETED fire in quick succession. Fulfil the order once ORDER_COMPLETED is received.
  • Manual capture: ORDER_AUTHORISED fires first. Capture the payment to complete the transaction, or cancel the order to decline. ORDER_COMPLETED fires after you capture.

Examples

Example with minimal required parameters

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

const clickToPayButton = document.getElementById('click-to-pay-button')

clickToPayButton.addEventListener('click', async () => {
  const instance = clickToPay({
    createOrder: async () => {
      // Call your backend here to create an order and return order.token
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    onSuccess: ({ orderId }) => {
      console.log('Payment successful!', orderId)
      window.location.href = `/confirmation?orderId=${orderId}`
    },

    onError: ({ error, orderId }) => {
      console.error('Payment failed:', error.message, orderId)
      alert(`Payment failed: ${error.message}`)
    },
  })

  instance.show()
})

Example with additional parameters

my-app.js
import RevolutCheckout from '@revolut/checkout'

const { clickToPay } = await RevolutCheckout.payments({
  publicToken: 'pk_abcdef12347890_...', // Merchant public API key
  mode: 'prod',
})

const clickToPayButton = document.getElementById('click-to-pay-button')

clickToPayButton.addEventListener('click', async () => {
  const instance = clickToPay({
    createOrder: async () => {
      // Call your backend here to create an order and return order.token
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    billingAddress: {
      countryCode: 'GB',
      region: 'London',
      city: 'London',
      streetLine1: '1 Revolut Street',
      streetLine2: '',
      postcode: 'EC2V 7HN',
    },

    onSuccess: ({ orderId }) => {
      console.log('Payment successful!', orderId)
      window.location.href = `/confirmation?orderId=${orderId}`
    },

    onError: ({ error, orderId }) => {
      console.error('Payment failed:', error.message, orderId)
      alert(`Payment failed: ${error.message}`)
    },

    onCancel: ({ orderId }) => {
      console.log('Payment cancelled', orderId)
      alert('Payment was cancelled.')
    },
  })

  instance.show()
})

Implementation checklist

Click to Pay is not available in the Sandbox environment. Integration testing requires a production merchant account.

Before deploying your Click to Pay implementation to your live website, complete the checklist below to ensure everything works as expected.

General checks

  • Click to Pay widget opens correctly when the customer clicks the payment button.

  • Your backend creates the order successfully when the widget is triggered.

  • Widget successfully calls createOrder function to fetch and use the order token.

  • Customer can authenticate with their card network (Visa or Mastercard) within the pop-up.

  • Payment flow:

    • Recognised customers can complete payment without manual card entry.
    • Returning but unrecognised customers can verify with OTP and select saved cards.
    • New customers can enter card details and complete payment.
    • Payment completes successfully and onSuccess callback fires.
    • Customer is redirected to a confirmation page.
  • Error handling works as expected:

    • Failed payments trigger the error callback (onError).
    • Customer sees appropriate error messaging.
    • Cancel callback (onCancel) is triggered when payment is abandoned.

Webhook verification

  • Webhook endpoint is set up to receive order and payment updates.
  • Webhook subscription created with ORDER_AUTHORISED, ORDER_COMPLETED, ORDER_PAYMENT_DECLINED, ORDER_PAYMENT_FAILED, ORDER_CANCELLED, and ORDER_FAILED events.
  • ORDER_COMPLETED webhook event is received when a payment completes successfully.
  • If using manual capture, ORDER_AUTHORISED is received after authorisation, and ORDER_COMPLETED is received after you capture the payment.
  • Your backend only fulfils orders after receiving the ORDER_COMPLETED webhook event - not on ORDER_AUTHORISED.
  • Webhook signature verification is implemented for security.

Click to Pay specific checks

  • Billing address collected on your frontend is correctly passed to the SDK via the billingAddress parameter for payment processing.
  • Country availability confirmed for target markets - see the country availability list.
  • Widget handles all three consumer journeys (recognised, returning, new) correctly.
  • Instance is properly destroyed when the widget is closed or the page is unloaded (call .destroy() in your cleanup logic).

Once your implementation passes all the checks, you can confidently deploy it to your live website.

These checks only cover the implementation path described in this tutorial. If your application handles more features of the Merchant API, see the Implementation checklists.

Congratulations! You've successfully implemented Click to Pay and are ready to accept payments with a faster, more secure checkout experience.

What's next

Click to Pay Web SDK reference
Full SDK reference for the web SDK
Order and payment lifecycle
Understand how orders and payments are processed
Using webhooks
Track the payment lifecycle with server-to-server webhooks
Decline reasons
Understand why card payments may be declined
Refunds
Learn how to refund your orders
Order management
Explore the full capabilities of the Orders API
Rate this page