Add the Revolut Pay button to your app, confirm payment results in your UI, and fulfil orders on authoritative webhooks.
Revolut Pay allows your customers to pay with their card or their Revolut Retail or Revolut Business account, delivering a streamlined checkout experience through your iOS application.
In this tutorial, we'll guide you through integrating Revolut Pay, powered by the Revolut Pay SDK for iOS. The SDK handles the entire payment flow, including presenting native payment sheets, biometric authentication, and redirecting to the Revolut app if necessary, reducing friction and improving conversion rates.

How it works
From an implementation perspective, the Revolut Pay iOS SDK works with the following components:
- Server-side: A server-side endpoint is required to securely communicate with the Merchant API to create orders.
- Client-side: The SDK is configured in your app with your public API key. When the customer taps the Revolut Pay button, the SDK uses the
tokenobtained from your server to initiate and process the payment. The SDK handles the entire payment flow, including presenting native sheets, handling biometric authentication, and redirecting to the Revolut app if necessary. - Endpoint for webhooks: Your server should listen for webhook events to reliably track the payment lifecycle and handle critical backend processes like updating order status, managing inventory, or initiating shipping. For more information, see: Use webhooks to keep track of the payment lifecycle.
Available versions
The Revolut Pay iOS SDK is available in two variants:
| SDK | Description |
|---|---|
| Revolut Pay Native (recommended) | Initialises Revolut Pay UI within your app, offering a seamless experience regardless of whether the Revolut retail app is installed. Integrating this version will add approximately 45 MB to your app's total size. |
| Revolut Pay Lite | Triggers a redirect to the Revolut retail app to make the payment. If the Revolut retail app is not installed, the SDK falls back to a WebView within your app. All 2.x.x versions are now deprecated in favour of RevolutPayLite. |
Both Revolut Pay Native and Revolut Pay Lite support savePaymentMethodForMerchant for Merchant Initiated Transactions (MIT).
The payment flow for each variant:
With Revolut Pay Native, the entire payment experience, including login and payment authorisation, occurs within a native interface inside your app. This provides the most seamless experience for the user, regardless of whether they have the Revolut app installed.
- The customer taps the Revolut Pay button in your app.
- Your app obtains an order
tokenfrom your server and passes to the SDK for initialisation. - The SDK presents a native payment sheet where the customer reviews and authorises the payment, potentially using biometrics or passkeys.
- The SDK processes the payment and returns the result to your app via a completion handler. You should use this result to update your UI (e.g., show a success or failure message).
- Your server receives webhook notifications to confirm the payment's final state and securely fulfil the order.
By default, the SDK prioritises the Retail sign-in flow. If Revolut Pay for Business is enabled for your merchant account, you can control which flow the checkout opens with the preferredMode parameter - see Additional features.
Implementation overview
The high-level implementation steps are the following:
- Set up an endpoint for creating orders
- Configure your Xcode project
- Initialise the SDK
- Add the Revolut Pay button
- Handle payment result
To see a working example, you can skip ahead.
Before you begin
Before you start this tutorial, 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 API keys.
- A backend server to securely create orders using the Merchant API. Your secret API key must never be exposed in your app.
- An iOS app with a checkout screen where you'll display the Revolut Pay button.
Implement Revolut Pay on iOS
This section walks you through the server- and client-side implementation step by step.
1. Set up an endpoint for creating orders
Before implementing the client-side SDK, 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 in your iOS application.
The role of this server-side endpoint is to act as a secure bridge between your app and the Merchant API. When a customer taps the Revolut Pay button in your app, your app will call this endpoint. Your endpoint is then responsible for:
- Receiving the checkout details (e.g.,
amount,currency) from the app request. - Securely calling the Merchant API: Create an order endpoint with the received details.
- Receiving the order details, including the public
token, back from the Merchant API. - Passing this
tokenback to your app in the response.
Later, the createOrder callback calls this endpoint to fetch the token the SDK needs to initialise and process the payment.
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-08-17
{
"amount": 1000,
"currency": "GBP"
}| Parameter | Description |
|---|---|
amount | The order amount in minor currency unit (e.g., cents). For £10.00, use 1000. |
currency | 3-letter ISO 4217 currency code for the payment. See supported currencies. |
Replace the Revolut-Api-Version value with your target API 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. Configure your Xcode project
This section covers the one-time setup tasks required to prepare your Xcode project for the SDK.
2.1 Install the SDK
The minimum supported iOS version is 15.0.
Check the available versions section to determine which SDK variant is right for your needs.
CocoaPods
We recommend using CocoaPods to integrate the SDK into your Xcode project.
-
Ensure you have the latest version of CocoaPods installed.
-
If your project doesn't have a
Podfileyet, create one by running:Terminalpod init -
Add the following line to your
Podfile:Podfilepod 'RevolutPayments/RevolutPay' -
At the end of your
Podfile, add this configuration:Podfilepost_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' end end end end -
Install the dependencies:
Terminalpod install -
From now on, open your project using the
.xcworkspacefile rather than the.xcodeprojfile. -
To ensure the SDK uses the latest version, run:
Terminalpod update RevolutPayments/RevolutPayRun this command to update the SDK to a newer version in the future.
2.2 Configure project capabilities
To allow your app to open the Revolut Retail and Revolut Business apps, you must declare the revolut and revolut-business URL schemes in your Info.plist file.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>revolut</string>
<string>revolut-business</string>
</array>| URL scheme | Description |
|---|---|
revolut | Lets the SDK detect the Revolut Retail (personal) app. |
revolut-business | Lets the SDK detect the Revolut Business app. |
Both schemes are required for preferredMode to work correctly.
If revolut-business is not added, the Revolut Business app cannot be detected, even if it is installed on the customer's device. In that case, Business app routing may be skipped and the checkout may fall back to another available Revolut Pay flow.
2.3 Enable biometrics and passkeys support
This step is only required when implementing Revolut Pay Native.
To offer users a seamless and secure login experience with Face ID, Touch ID, and passkeys, you must associate your app with our web service.
Configure your App ID for Passkeys
To configure passkeys, you need your App ID, which is a combination of your App ID Prefix and your Bundle ID.
It's crucial to get the correct App ID Prefix. This may be different from the Team ID shown in your main Apple Developer account page or in Xcode, especially for older developer accounts or apps that have been transferred between accounts.
To find the correct App ID Prefix and Bundle ID:
- Log in to your developer account at developer.apple.com >
Certificates, IDs & Profiles>Identifiers. - Select your app from the list.
- Here you will find the App ID Prefix (e.g.,
ABCDE12345) and your app's Bundle ID (e.g.,com.yourcompany.yourapp).
Your final App ID will be in the format: <App ID Prefix>.<Bundle ID>, based on our example: ABCDE12345.com.yourcompany.yourapp.
Register your App ID with Revolut
Email your verified App ID to merchant-integration@revolut.com.
Add the Associated Domain to your project
Once Revolut confirms your App ID registration, you must link your app to the Revolut domain.
- In your Xcode project, go to Signing & Capabilities.
- Click + Capability and add the Associated Domains capability.
- In the domains field, add the following entry:
webcredentials:revolut.com.
Important update for SDK 3.9+
Starting with version 3.9, you must use webcredentials:revolut.com.
The previous domain, webcredentials:sso.revolut.com, is now deprecated. Please ensure your Associated Domains configuration is updated.
Migrating from 3.9>
If you are updating from a version of the SDK prior to 3.9.0, you will need to update your Associated Domains configuration.
- In your Xcode project, navigate to Signing & Capabilities.
- Find the Associated Domains section.
- Replace the old entry
webcredentials:sso.revolut.comwith the new entrywebcredentials:revolut.com.
2.4 Add the URL handler
When the user is redirected from the Revolut app back to yours, you must pass the incoming URL to the SDK.
Your implementation depends on your app's lifecycle management. Use the UISceneDelegate method for modern, scene-based apps (the default since iOS 13), which support features like multiple windows on iPad. Use the AppDelegate method for older apps or if you have explicitly opted out of the scene-based lifecycle.
If you are using UISceneDelegate, add this to your SceneDelegate:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
RevolutPayKit.handle(url: url)
}
}3. Initialise the SDK
First, import the RevolutPayments module. Then, on app launch, typically in your AppDelegate, configure the SDK with your public API key and the environment.
import RevolutPayments
// In your AppDelegate's didFinishLaunchingWithOptions method
RevolutPaymentsSDK.configure(with: .init(
merchantPublicKey: "<yourPublicApiKey>",
environment: .production // or .sandbox for testing
))The RevolutPaymentsSDK.configure method accepts the following parameters:
| Parameter | Description |
|---|---|
merchantPublicKey | Your Merchant API public key used for authorisation. |
environment | The API environment the SDK operates in: .production or .sandbox for testing. Defaults to .production. |
4. Add the Revolut Pay button
On your checkout screen, create and display the Revolut Pay button.
4.1 Create the button
Instantiate RevolutPayKit and call the button method. This is where you connect your server-side endpoint and define what happens after the payment attempt.
let revolutPayKit = RevolutPayKit()
let button = revolutPayKit.button(
style: .init(size: .large),
returnURL: "myapp://revolut-pay", // Your app's custom URL scheme
createOrder: { createOrderHandler in
// 1. Call your server to create the order
createOrderOnBackEnd { orderToken in
// 2. Pass the token to the SDK
createOrderHandler.set(orderToken: orderToken)
}
},
completion: { result in
switch result {
case .success:
// Handle successful payment
case .failure(let error):
// Handle payment error
case .userAbandonedPayment:
// Handle abandoned payment
}
}
)The button method accepts the following core parameters:
| Parameter | Description |
|---|---|
style | Contains styling parameters of the button, e.g. size. |
returnURL | Upon payment completion, the Revolut app attempts to open this URL. This can be a Universal Link or a custom URL scheme. |
createOrder | A callback the SDK calls after the customer taps the button. Request an order token from your backend endpoint from step 1 and pass it to the createOrderHandler. |
completion | A closure called when the payment flow completes with a PaymentResult indicating success, failure, or user abandonment. |
For more information about the parameters and button styling, see: Parameters: iOS and Revolut Pay button guidelines.
4.2 Display the button
Add the created button to your view hierarchy.
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
button.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor),
button.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])Additional features
The Revolut Pay SDK also offers a range of optional features you can enable when creating the button.
Save payment method and Fast checkout are mutually exclusive - you can't enable both savePaymentMethodForMerchant and shouldRequestShipping in the same button initialiser.
Add a promotional banner
We recommend implementing the promotional banner. Analysis has shown that having the upsell widget can increase conversion to payment by ~5%.
You can use the promotional banner widget to offer rewards to customers who create a new Revolut account after checkout.
First, configure the banner by calling revolutPayKit.promotionalBanner method:
let promotionalBanner = revolutPayKit.promotionalBanner(
transactionId: "transaction-id", // Unique ID of the payment corresponding to the promotional offer
amount: 10_00,
currency: .EUR,
customer: .init()
)The promotionalBanner method accepts the following parameters:
| Parameter | Description |
|---|---|
transactionId | The ID of the transaction corresponding to the promotional offer. |
amount | The amount paid by the customer, in the currency's smallest unit (e.g., 1000 for £10.00). |
currency | The payment currency in ISO 4217 format. |
customer | An object containing customer details to pre-fill the offer. |
Then, add the banner to your view hierarchy, for example on your order confirmation screen:
view.addSubview(promotionalBanner)
promotionalBanner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
promotionalBanner.topAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.topAnchor,
constant: 16
),
promotionalBanner.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: -16
),
promotionalBanner.leadingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 16
)
])For more information about the possible parameters of the banner, see: Revolut Pay iOS SDK: RevolutPayKit.promotionalBanner.
Save payment method
Set savePaymentMethodForMerchant to true to save the customer's payment method for future merchant-initiated transactions (e.g., subscriptions). For more information, see: Charge a customer's saved payment method, Manage subscriptions.
let button = revolutPayKit.button(
style: .init(size: .large),
returnURL: "myapp://revolut-pay",
savePaymentMethodForMerchant: true,
createOrder: { createOrderHandler in
createOrderOnBackEnd { orderToken in
createOrderHandler.set(orderToken: orderToken)
}
},
completion: { result in
// Handle the payment result
}
)To save a payment method, you must either create a customer object during order creation or provide a customer.id for an existing customer.
For automated recurring billing built on saved payment methods, see the Subscriptions API introduction.
Collect shipping details with Fast checkout
Set shouldRequestShipping to true to enable Fast checkout. This allows Revolut Pay to collect the shipping address and delivery methods from the customer, letting your application skip the shipping flow and allowing the customer to use their details already stored in Revolut Pay.
let button = revolutPayKit.button(
style: .init(size: .large),
returnURL: "myapp://revolut-pay",
shouldRequestShipping: true,
createOrder: { createOrderHandler in
createOrderOnBackEnd { orderToken in
createOrderHandler.set(orderToken: orderToken)
}
},
completion: { result in
// Handle the payment result
}
)Your backend must support the Fast checkout flow for this feature to work. See the Fast checkout guide for details.
Control Retail and Business flows
If Revolut Pay for Business is enabled for your merchant account, customers can check out from both their Revolut Retail (personal) and Business accounts. By default, the checkout prioritises the Retail sign-in flow, and customers can switch between the two.
To use preferredMode, make sure that:
- The SDK is version 3.14.0 or later.
- Both URL schemes are declared as described in Configure project capabilities - otherwise the Revolut Business app cannot be detected.
Use the preferredMode parameter when creating the button to prioritise one of the flows, or restrict the checkout to a single flow:
let button = revolutPayKit.button(
style: .init(size: .large),
returnURL: "myapp://revolut-pay",
preferredMode: .business, // .retail (default), .retailOnly, .business, or .businessOnly
createOrder: { createOrderHandler in
createOrderOnBackEnd { orderToken in
createOrderHandler.set(orderToken: orderToken)
}
},
completion: { result in
// Handle the payment result
}
)Each value supports a different use case:
| Value | Behaviour | Use case |
|---|---|---|
.retail (default) | Prioritises the Retail flow. Customers can still switch to Revolut Business. | Checkouts aimed mainly at retail customers. |
.business | Prioritises the Business flow. Customers can still switch to Revolut Retail. | Checkouts with mostly business customers, where retail payments stay available. |
.retailOnly | Restricts the checkout to the Retail flow. Switching to Revolut Business is not offered. | Retail-only checkouts. |
.businessOnly | Restricts the checkout to the Business flow. Switching to Revolut Retail is not offered. | B2B-only checkouts. |
Both Revolut Pay Native and Revolut Pay Lite support preferredMode. In the Business flow, the checkout redirects the customer to the first of the following that is available:
- The Revolut Business app.
- The Revolut Retail app.
- A Business web checkout opened in a WebView inside your app, where the customer signs in and completes the payment.
If Revolut Pay for Business is not enabled for your merchant account, the Business flow is skipped and the checkout opens the Retail flow regardless of preferredMode.
For the full description of each value, see: PreferredMode enumeration.
When the customer pays with a saved payment method, the checkout always opens the Retail flow regardless of preferredMode - saved payment method consent is currently supported only in the Retail flow.
Other features
Use these parameters to fine-tune the checkout to your needs: match the button to your brand's look and feel, or speed up checkout for returning customers by pre-filling their details.
let button = revolutPayKit.button(
style: .init(size: .large, variants: .init(lightMode: .dark, darkMode: .light), radius: .small),
returnURL: "myapp://revolut-pay",
customer: .init(name: "John Smith", email: "john.smith@example.com"),
createOrder: { createOrderHandler in
createOrderOnBackEnd { orderToken in
createOrderHandler.set(orderToken: orderToken)
}
},
completion: { result in
// Handle the payment result
}
)5. Handle payment result
Revolut Pay reports the payment result to your app through the completion block of the button method. This confirms the result at the UX-level only.
The authoritative payment status for your business logic - fulfilment, shipping, inventory - always arrives on your backend via webhooks, which you set up at the end of this step.
Before handling results, understand the roles of client-side results and server-side webhooks:
- Completion handler (
PaymentResult): This is perfect for handling frontend logic, such as displaying a success message, updating the UI, or showing an error to the customer. However, its delivery is not guaranteed - it might indicate a failure even if the customer later completes the payment, for example when they initially abandon the flow or a transient issue occurs during payment processing. - Webhooks: These are server-to-server notifications that we guarantee to send for every payment status change. You must rely on webhooks for all critical backend logic, such as confirming the order, releasing digital goods, or starting the shipping process.
Never use the completion handler as the sole trigger for critical backend logic.
Confirm payment result in UI
The completion block passed to the button method provides a PaymentResult that tells you whether the payment succeeded, failed, or was abandoned by the customer. Handle each case to show an appropriate message to the user:
completion: { result in
switch result {
case .success:
// Present the payment confirmation UI
case .failure(let error):
// Show an error message and let the customer retry
case .userAbandonedPayment:
// Return the customer to your checkout screen
}
}PaymentResult case | What to do |
|---|---|
.success | Present the payment confirmation UI. Treat it as a UX-level confirmation only - fulfilment, shipping, and inventory updates should wait for the authoritative webhook. |
.failure(error) | Show an error message and let the customer retry the payment. For the full list of possible errors, see RevolutPayKit.Error. |
.userAbandonedPayment | Return the customer to your checkout screen and offer alternative payment methods. |
A .failure result is not necessarily final. The completion handler might indicate a failure even if the customer later completes the payment, such as when they initially abandon the flow or a transient issue occurs during payment processing.
Always verify the final payment status with your backend before treating the order as permanently failed.
Set up webhooks
Your backend must rely on webhooks as the authoritative source of payment status. The completion handler confirms the result at the UX level only - webhooks are guaranteed, server-to-server notifications that drive your critical backend logic.
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
Revolut-Api-Version: 2026-08-17
{
"url": "https://example.com/webhooks",
"events": [
"ORDER_COMPLETED",
"ORDER_AUTHORISED",
"ORDER_PAYMENT_DECLINED",
"ORDER_PAYMENT_FAILED",
"ORDER_CANCELLED",
"ORDER_FAILED"
]
}| Parameter | Description |
|---|---|
url | Your webhook URL. Must be a valid HTTP or HTTPS URL capable of receiving POST requests. |
events | Array of event types to subscribe to. See Webhook events for the full list. |
For detailed information on setting up webhooks, see: Use webhooks to track order and payment lifecycle.
Payment outcomes
Handle the following scenarios in your webhook handler:
The payment has been authorised by the card network or the customer's Revolut account.
Webhooks 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.
Example
For a complete, working implementation, refer to the example app included in our public SDK repository:
To run the example app:
- Clone the repository.
- Navigate to the
ExampleAppdirectory within the latest release. - Run
make projectto install dependencies. - Open
RevolutPayApp.xcworkspacein Xcode and run the project.
Implementation checklist
The Sandbox environment is designed to replicate the production environment's behaviour, with the key difference being the absence of app redirection due to the lack of a sandbox version of the Revolut retail app.
For more information about Revolut Pay payment flows in Sandbox, see: Test flows.
Before going live, use this checklist to test your integration in both the .sandbox and .production environments.
Project setup
-
SDK is installed via CocoaPods.
-
Info.plistcontains therevolutandrevolut-businessURL schemes. -
Associated Domain
webcredentials:revolut.comis configured for passkeys.Double-check your Associated Domain, if you used an SDK version before 3.9.
-
The SDK is initialised with the correct public API key and environment.
-
The Privacy Manifest is correctly merged into the app's resources.
Payment flow
- The Revolut Pay button appears correctly on the checkout screen.
- Tapping the button successfully calls your server to create an order and receives a
token. - Test case 1 (Revolut app installed): The payment flow completes via the native in-app experience (Revolut Pay Native) or via redirect to the Revolut app and back (Revolut Pay Lite).
- Test case 2 (Revolut app not installed): The payment flow completes successfully within the in-app flow (Revolut Pay Native) or in-app WebView (Revolut Pay Lite fallback).
- Test case 3 (Biometrics): Passkey/Face ID/Touch ID login works as expected (requires a real device).
- A successful payment shows a success message in the UI.
- A failed payment shows an appropriate error message in the UI.
- The user cancelling the payment flow is handled gracefully.
Backend verification
- Your server correctly receives and processes webhook events for different payment states (e.g.,
ORDER_COMPLETED,ORDER_AUTHORISEDfor success;ORDER_PAYMENT_FAILED,ORDER_CANCELLEDfor failure or cancellation). - Order fulfilment logic is only triggered after receiving a successful webhook, not from the client-side callback.
Congratulations! You've successfully implemented Revolut Pay. Your app is ready to submit for App Store review - once approved, you're ready to accept your first payment.
Prepare for App Store submission
To comply with Apple's App Store requirements, you must include our privacy manifest in your app. This file, named PrivacyInfo.xcprivacy, details the data collection practices of the Revolut Pay SDK.
Because the Revolut Pay SDK is distributed as a static library, you must manually merge our PrivacyInfo.xcprivacy file with your app's primary privacy manifest.
These steps are required to prepare your iOS application for Apple's review policies and ensure that your Revolut Pay integration adheres to their privacy standards.
For merchants using Revolut Pay Native, privacy manifests are available from version 3.2.1.
Locate the privacy manifest
The privacy manifest file is located within the RevolutPayments.zip archive under Frameworks/RevolutPay/RevolutPayNativeSDK.bundle/PrivacyInfo.xcprivacy.
Download and extract the privacy manifest
Download the archive from the latest Release and extract the privacy manifest.
Add the privacy manifest to your app's resources
- If your app does not currently use a privacy manifest, simply add the SDK's file to your app's resources.
- If you already have an existing privacy manifest, merge the SDK's manifest with your app's existing privacy document to ensure compliance with Apple's new requirements. Address any conflicts by integrating data handling practices from both the SDK and your app into a unified document.