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.

- 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:
- Server-side: expose an endpoint on your backend that creates an order using the Merchant API: Create an order endpoint.
- 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.
- 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:
- The customer goes to the checkout page, chooses to pay via Click to Pay and clicks the payment button.
- The widget opens the Click to Pay pop-up and calls your
createOrderfunction to create the order via your backend in the background. - The customer authenticates with their card network (Visa or Mastercard) within the pop-up.
- The card network performs 3DS authentication and securely delivers the payment credentials to Revolut.
- Revolut authorises the payment, just like a standard card payment.
- The
onSuccesscallback fires - the payment has been authorised. - Your server receives the
ORDER_COMPLETEDwebhook event (orORDER_PAYMENT_DECLINED/ORDER_PAYMENT_FAILED/ORDER_CANCELLEDif 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:
- Set up an endpoint for creating orders
- Install the SDK
- Initialise the SDK
- Configure and mount the widget
- 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:
- Receiving the checkout details (e.g.,
amount,currency) from the frontend request. - Securely calling the Merchant API: Create an order endpoint with the received details.
- Receiving the order details from the Merchant API, including the public
token. - Passing this
tokenback 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:
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"
}| Parameter | Description | Required |
|---|---|---|
amount | The order amount in minor currency unit (e.g., cents). For £10.00, use 1000. | Yes |
currency | 3-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/checkoutyarn add @revolut/checkoutpnpm add @revolut/checkoutbun add @revolut/checkoutAlternatively, 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.
import RevolutCheckout from '@revolut/checkout'
const { clickToPay } = await RevolutCheckout.payments({
publicToken: 'pk_abcdef12347890_...', // Merchant public API key
mode: 'prod',
})
// Configuration code will go here| Parameter | Description | Type | Required |
|---|---|---|---|
publicToken | Your Merchant API public key | string | Yes |
mode | API environment. Default: 'prod' | 'prod' | 'sandbox' | No |
locale | Widget language (defaults to 'auto' for automatic detection) | Locale | No |
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.
<!-- ... -->
<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.
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.
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 snippet | Description |
|---|---|
RevolutCheckout.payments(...) | Initialises the Revolut Checkout payments module and returns an object containing the clickToPay factory. Pass your publicToken and mode here. |
clickToPay | Creates a Click to Pay instance when called with your options. It returns a ClickToPayInstance. |
ClickToPayInstance | The 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. |
createOrder | A 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 methods | Call .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.
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.
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:
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()
})| Callback | Description |
|---|---|
onSuccess | Fires 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. |
onError | Fires when an error occurs. Receives RevolutCheckoutError. Use it to display error messaging, log diagnostics, or prompt the user to try again. |
onCancel | Fires 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.
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:
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"
]
}| Parameter | Description | Required |
|---|---|---|
url | Your webhook URL. Must be a valid HTTP or HTTPS URL capable of receiving POST requests. | Yes |
events | Array 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 event | Description |
|---|---|
ORDER_AUTHORISED | The payment has been authorised but not yet captured |
ORDER_COMPLETED | The payment has been captured and completed |
What to do:
- Automatic capture (default): both
ORDER_AUTHORISEDandORDER_COMPLETEDfire in quick succession. Fulfil the order onceORDER_COMPLETEDis received. - Manual capture:
ORDER_AUTHORISEDfires first. Capture the payment to complete the transaction, or cancel the order to decline.ORDER_COMPLETEDfires after you capture.
Examples
Example with minimal required parameters
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
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
createOrderfunction to fetch and use the ordertoken. -
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
onSuccesscallback 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.
- Failed payments trigger the error callback (
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, andORDER_FAILEDevents. -
ORDER_COMPLETEDwebhook event is received when a payment completes successfully. - If using manual capture,
ORDER_AUTHORISEDis received after authorisation, andORDER_COMPLETEDis received after you capture the payment. - Your backend only fulfils orders after receiving the
ORDER_COMPLETEDwebhook event - not onORDER_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
billingAddressparameter 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.