Getting started with Tap to Pay on iPhone involves server-side and browser interactions that must be completed before any SDK code is written. This page covers app registration in the Developer Portal, the OAuth consent flow between your backend and the merchant's browser, and the token exchange that provides short-lived credentials to the iOS device.
Once you complete these steps, move on to the Integration guide to install the SDK and implement the payment flow.
Access management
Before diving into the implementation, understand the key concepts that underpin the Tap to Pay authentication model.
Tap to Pay on iPhone is a partner integration: your app acts on behalf of a merchant who has a Revolut Business account with payment acceptance enabled for their Merchant account. The merchant must explicitly authorise your application to access Tap to Pay on their behalf - this is done through the OAuth 2.0 authorisation code flow with PKCE (Proof Key for Code Exchange).
What is a scope
A scope limits what an application can do, such as which API endpoints it can call. The tap_to_pay_sdk scope authorises your application to use the Tap to Pay SDK and its associated endpoints. When first authorising an application, the merchant must consent to the scope's access.
What is PKCE
PKCE (Proof Key for Code Exchange, defined in RFC 7636) is an extension to the OAuth 2.0 authorisation code flow that protects against authorisation code interception. In a standard OAuth flow, a client secret is used to authenticate the token exchange. Mobile and desktop apps can't safely store a client secret, so PKCE replaces it with a one-time cryptographic challenge:
- Your backend generates a random
code_verifierstring. - Your backend sends a derived
code_challenge(SHA-256 hash of the verifier) when redirecting the merchant to the consent page. - After the merchant approves, Revolut returns an authorisation code.
- Your backend exchanges the authorisation code for tokens, sending the original
code_verifieralongside. - Revolut hashes the verifier and compares it to the challenge sent earlier - if they match, the token exchange succeeds.
This ensures that even if an attacker intercepts the authorisation code, they can't exchange it for tokens without the verifier. For the full step-by-step implementation, see Step 2: Get merchant consent.
The following diagram shows how the three parties interact during the PKCE flow:
Integration architecture
You can implement the OAuth flow for Tap to Pay in the following ways. Choose the one that best fits your use case.
Your backend handles the entire OAuth flow - consent, token exchange, and token storage. The iOS app requests short-lived device tokens from your backend.
Recommended for merchants with multiple employees or multiple iPhones - the merchant authorises once, and all devices receive tokens from your backend. Requires backend credential storage and token refresh logic.
Token architecture
The token architecture depends on your chosen integration architecture:
Treat your iOS app as an untrusted client for long-lived credentials. Keep consent, refresh, and token exchange logic on your backend, and let the app request fresh device tokens when needed.
| Component | Responsibility |
|---|---|
| Your backend | Stores the merchant's access_token and refresh_token securely. Exchanges the access token for short-lived device tokens. Serves device tokens to the iOS app via an authenticated endpoint. |
| Your iOS app | Requests short-lived device tokens from your backend via the TokenProviderProtocol. Never stores long-lived OAuth credentials. Calls the SDK to start payment sessions. |
| Revolut Tap to Pay SDK | Uses the device token for pairing and payment API calls. Requests a new token when the current one expires or receives an unauthorised error. Calls onLogout after two consecutive unauthorised errors - meaning the merchant is no longer authorised (consent revoked, not granted, or app permission removed). |
Token lifecycle
The OAuth flow produces two types of tokens:
- long-lived credentials (
access_tokenandrefresh_token) that are stored securely, and - short-lived device tokens that the iOS device receives for SDK use (backend-led architecture only).
Understanding the lifetime and refreshability of each token type helps you design a reliable token provisioning strategy.
| Token type | Lifetime | Refreshable | Stored on |
|---|---|---|---|
access_token | ~30 minutes | Yes - use refresh_token to obtain a new one | Backend (backend-led) / iOS app keychain (app-led) |
refresh_token | 7 days | N/A | Backend (backend-led) / iOS app keychain (app-led) |
| Device token | ~30 minutes | No - request a new one from your backend | iOS device (short-lived, backend-led only) |
In the backend-led architecture: Never expose the access_token or refresh_token to the client device. Your iOS app should only ever receive short-lived device tokens obtained via the token exchange flow.
In the app-led architecture: The app holds the access_token and refresh_token directly. Store them securely in the iOS keychain — never in plain text or unprotected storage.
Grant types
The OAuth token endpoint accepts different grant_type values depending on the operation being performed. You'll encounter three grant types throughout this guide:
| Grant type | Description | Used in |
|---|---|---|
authorization_code | Exchanges the authorisation code for an access_token and refresh_token. | Step 2.4 |
refresh_token | Refreshes the tokens and returns new ones when the access_token expires. | Refresh the access token |
exchange_token | Exchanges the merchant's access_token for a short-lived device token. Specific to the backend-led architecture. | Step 3: Get a device token |
How it works
The steps below cover the backend-led OAuth architecture (recommended). If you're using the app-led approach, Steps 2 and 3 are handled directly in the iOS app - see Integration architectures.
The setup flow involves three main steps, all performed before touching the SDK:
- Register your application in the Developer Portal, enable the Tap to Pay scope, set up sandbox authentication, and submit to production.
- Get merchant consent by implementing the OAuth 2.0 authorisation code flow with PKCE. The merchant authorises your application, and your backend receives long-lived OAuth tokens.
- Get a device token - your backend exchanges the merchant's OAuth token for a short-lived device token that the iOS app uses with the SDK.
Implementation overview
Before you begin
Before you start, ensure you have the following:
- Revolut Business account with Merchant features enabled.
-
API & integrationspermission enabled on the merchant's Revolut Business account. Without this, the merchant will receive an error when accessing the OAuth consent page. - A backend able to securely store OAuth tokens and perform server-to-server API calls.
- An iPhone XS or newer with iOS 16.4 or later (for later SDK testing).
Sandbox is currently not supported for Tap to Pay.
Testing must be done in the production environment with real transactions.
Set up Tap to Pay authentication
The following steps guide you through registering your application, obtaining merchant consent, and provisioning device tokens - all server-side and browser interactions that must be completed before installing the SDK.
1. Register your application
Register your partner application and enable the Tap to Pay scope in the Developer Portal.
1.1 Create a new app
- Log in to the Developer Portal.
- Click Add new to create a new application.
- Give a name to the application. You may also choose to upload an application icon.
Don't have a Developer account yet?
1.2 Enable the Tap to Pay scope
- Go to Settings > Scopes.
- Enable Accept Payments with Tap to Pay on iPhone through a 3rd party application.
- Click Continue.
1.3 Set up Sandbox authentication
Sandbox authentication is required to submit your app for review and approval in the Developer Portal. However, Tap to Pay does not currently support the sandbox environment - these credentials are needed only for the app approval process, not for SDK testing.
-
Configure the Redirect URLs for the OAuth flow.
-
Configure one or more allowed Bundle IDs for the iOS apps embedding the SDK.
-
Generate a CSR and upload it (or upload an OBIE/eIDAS certificate):
A Certificate Signing Request (CSR) is a file your backend generates to request a digital certificate from a certificate authority. The Developer Portal uses the CSR to issue sandbox certificates that authenticate your application's API calls. You can generate a CSR using
openssl:openssl req -new -newkey rsa:2048 -nodes -out revolut.csr \ -keyout private.key \ -subj '/C=<COUNTRY_CODE>/ST=<STATE_OR_PROVINCE>/L=<LOCALITY>/O=<YOUR_APP_NAME>/OU=<ORG_UNIT>/CN=<COMMON_NAME>' \ -sha256 -outform derParameter Description Example CTwo-letter country code GBSTState or province EnglandLLocality (city) LondonOYour application or organisation name My Taxi AppOUOrganisation Unit ID - provided by Revolut in the Developer Portal 001580000103UAvAAMCNCommon Name - provided by Revolut in the Developer Portal 2kiXQyo0tedjW2somjSgH7You can copy the full
opensslcommand with theOUandCNvalues pre-filled from the Sandbox authentication tab in the Developer Portal.For OBIE/eIDAS certificate alternatives, see Certificate types.
-
Click Continue.
While sandbox authentication is required for app approval, Tap to Pay is not currently supported in sandbox - testing must be done in the production environment with real transactions.
1.4 Submit to production
- Click the Submit to production widget. The scopes you enabled earlier are already loaded.
- Configure the Production endpoints > Redirect URLs.
- Configure the iOS Applications > Bundle IDs for production.
- Upload a production certificate in the same format as the sandbox CSR (or OBIE/eIDAS certificate).
- Click Continue.
After submitting, your application is assigned a Production Client ID (visible under the Overview tab) and the production environment becomes available for integration. Your app enters In review status - the review is triggered automatically, no additional action is required. You can begin implementing and testing with the production credentials during the review period.
The bundle ID is used during the Tap to Pay pairing process. The Card Reader Token issued for Apple's ProximityReader framework includes the allowed app bundle ID. If your iOS app's bundle ID is not registered correctly, device pairing will fail.
2. Get merchant consent
The merchant consent flow is triggered from your app - typically from a settings screen, payment setup page, or hardware pairing flow where the merchant connects their Revolut Business account. When the merchant clicks a "Connect your Revolut account" button (or similar), your app redirects them to Revolut's consent page to authorise your application.
This is a one-time setup for the merchant. Once consent is granted, your backend stores the OAuth tokens and can refresh them automatically - the merchant doesn't need to repeat this flow. Re-consent is only needed if the merchant revokes permission or the tokens can no longer be refreshed (see onLogout handling in the integration guide).
Your integration must use the OAuth 2.0 authorisation code flow with PKCE. The merchant grants consent to your application for the tap_to_pay_sdk scope, and your backend receives long-lived OAuth tokens in return.
2.1 Generate the PKCE verifier
To start the PKCE flow, generate the following values on your backend (or locally and pass them to your backend) using a CLI:
| Value | Description | Used in |
|---|---|---|
code_verifier | A random 32-byte string, base64url-encoded without padding. This is the secret your backend keeps and sends during the token exchange. | Step 2.4 |
code_challenge | The SHA-256 hash of the code_verifier, base64url-encoded without padding. This is sent to Revolut when redirecting the merchant to the consent page so Revolut can verify the exchange later. | Step 2.2 |
Store them in your backend's session state or environment, so they're available for the subsequent steps:
# 1. Generate 32 random bytes and base64url-encode them - this is your code_verifier
CODE_VERIFIER=$(head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '=')
# 2. SHA-256 hash the verifier and base64url-encode the digest - this is your code_challenge
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=')The code challenge must be exactly 32 bytes after SHA-256 hashing. Incorrect encoding will be rejected.
2.2 Send the consent request
Redirect the merchant's browser to Revolut's consent page by constructing a GET request to the consent endpoint with query parameters. The code_challenge generated in Step 2.1 is included as a query parameter so Revolut can verify the token exchange later.
When the merchant's browser loads this URL, they see a consent page where they can review and approve your application's request to access Tap to Pay on their behalf.
GET https://business.revolut.com/partner-confirm?response_type=code&scope=tap_to_pay_sdk&client_id=ae529bb9-eeb9-4065-b0f0-fa395c6e38f6&redirect_uri=https://yourapp.com/callback&code_challenge_method=S256&code_challenge=2MjyE9vGwLylv5_L_KKNF-LNMJpakVEVHQfdgWwatoQ&state=xyz123The request consists of the consent endpoint and the following query parameters:
| Environment | Base URL |
|---|---|
| Production | https://business.revolut.com/partner-confirm |
| Parameter | Value | Description |
|---|---|---|
response_type | code | Fixed value |
scope | tap_to_pay_sdk | Fixed value - must be tap_to_pay_sdk for Tap to Pay |
client_id | {your_client_id} | From Step 1 |
redirect_uri | {your_redirect_uri} | The URI Revolut redirects to after consent. Depends on your implementation - it could be a callback to your website, the iOS app, or any other URL you choose to handle the OAuth redirect. Must match a URI registered in the Developer Portal. |
code_challenge_method | S256 | Fixed value |
code_challenge | {code_challenge} | Computed in Step 2.1 |
state | {random_string} | Opaque value to prevent CSRF - generate a random string, send it here, and verify it matches when the redirect comes back in Step 2.3 |
The merchant must have the API & integrations permission enabled on their Revolut Business account. Without this permission, they will receive an error when trying to access the consent page.
The consent page has no deny button - the merchant can either click Authorise or close the page. If they close it without authorising, no redirect is sent back to your application. The merchant can rerun the consent flow from wherever it was initiated in your app (for example, from an admin settings screen).
After the merchant clicks Authorise, Revolut redirects their browser back to your application.
2.3 Handle the redirect
Your backend must capture the query parameters from the redirect that Revolut sends to the redirect_uri you registered in Step 1 and specified in the consent request:
GET https://yourapp.com/callback?code={authorisation_code}&state={state}| Parameter | Description |
|---|---|
code | The authorisation code (one-time use, short-lived). Use this to exchange for tokens in Step 2.4. |
state | Must match the state you sent in Step 2.2. If it doesn't match, reject the response - this indicates a possible CSRF attack. |
The authorisation code is short-lived. Exchange it for tokens as soon as possible.
2.4 Exchange the authorisation code for tokens
Your backend sends a POST request to the OAuth token endpoint with the authorisation code and the code_verifier. Revolut checks if the verifier matches the challenge sent in Step 2.2 and returns an access_token and a refresh_token in the response.
| Environment | Token endpoint |
|---|---|
| Production | https://merchant-secure.revolut.com/api/oauth/token |
POST https://merchant-secure.revolut.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id={your_client_id}
&grant_type=authorization_code
&code=oa_prod_abcdefgh123456-a1b2c3d-...
&code_verifier={code_verifier}Form parameters:
| Parameter | Value | Description |
|---|---|---|
client_id | {your_client_id} | Same client ID from Step 1 |
grant_type | authorization_code | Fixed value |
code | {authorisation_code} | The code from the redirect in Step 2.3 |
code_verifier | {code_verifier} | The same code_verifier string generated in Step 2.1 |
The code_verifier here is not the SHA-256 hash - it's the original random bytes, base64url-encoded. Revolut hashes it server-side and compares it against the code_challenge you sent in Step 2.2.
3. Get a device token
The iOS device should not use the merchant's long-lived OAuth token directly. Instead, your backend exchanges the OAuth access_token obtained in Step 2.4 for a short-lived device token that the iOS app can use with the SDK. This follows RFC 8693 (OAuth 2.0 Token Exchange) semantics.
To do this, your backend sends a POST request to the same OAuth token endpoint used in Step 2.4. The grant_type parameter determines the type of exchange - setting it to exchange_token tells Revolut to issue a short-lived device token derived from the merchant's access_token rather than performing an authorisation code exchange.
| Environment | Token endpoint |
|---|---|
| Production | https://merchant-secure.revolut.com/api/oauth/token |
POST https://merchant-secure.revolut.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id=ae529bb9-eeb9-4065-b0f0-fa395c6e38f6
&grant_type=exchange_token
&subject_token=oa_prod_veKzTCONV8TKU0qOrahK4peTQt15PzkEPGTmeHBfQSYForm parameters:
| Parameter | Value | Description |
|---|---|---|
client_id | {your_client_id} | Same client ID from Step 1 |
grant_type | exchange_token | Fixed value - triggers the device token flow |
subject_token | {backend_oauth_access_token} | The OAuth access token obtained in Step 2.4 |
The subject_token is the access token your backend holds - not the refresh token, and not the authorisation code.
Refresh the access token
The access_token obtained in Step 2.4 expires after approximately 30 minutes. When it expires, your backend needs to use the refresh_token to obtain a new access_token without re-prompting the merchant for consent.
Your backend sends a POST request to the same OAuth token endpoint, with grant_type=refresh_token and the stored refresh_token.
| Environment | Token endpoint |
|---|---|
| Production | https://merchant-secure.revolut.com/api/oauth/token |
POST https://merchant-secure.revolut.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id={your_client_id}
&grant_type=refresh_token
&refresh_token={refresh_token}Form parameters:
Once your backend has the new access_token, use it to request a new device token - see Step 3: Get a device token. The iOS app's TokenProviderProtocol will automatically request a fresh device token from your backend when the current one expires.
Implementation checklist
Use this checklist to verify that each part of the Tap to Pay authentication setup is correctly implemented before moving on to the Integration guide.
Since sandbox is not currently supported for Tap to Pay, verify your setup in the production environment using your Production Client ID.
App registration
- Application created in the Developer Portal.
- Tap to Pay scope enabled.
- Sandbox authentication configured (Redirect URLs, Bundle IDs, CSR/certificate uploaded).
- App submitted to production and Production Client ID obtained.
- Production redirect URLs and bundle IDs configured.
Merchant consent (OAuth PKCE)
-
code_verifierandcode_challengegeneration implemented on backend. - Consent URL constructed with all required query parameters.
- Redirect handler captures
codeandstatefrom the callback. -
stateparameter verified against the value sent in the consent request. - Authorisation code exchanged for
access_tokenandrefresh_token. -
code_verifiersent in the exchange (not the hash). -
access_tokenandrefresh_tokenstored securely on backend (never exposed to device).
Device token
- Token exchange endpoint implemented (
grant_type=exchange_token). -
subject_tokenuses theaccess_token(not refresh token, not auth code). - Backend endpoint exposed for the iOS app to request device tokens.
- Token refresh strategy implemented (request new device token when expired).
Token refresh
- Refresh endpoint implemented (
grant_type=refresh_token). - New
refresh_tokenfrom response stored immediately (replaces the old one). - Expired
access_tokenreplaced on backend when refresh succeeds.
You've completed the Tap to Pay authentication setup!
Your backend can now issue short-lived device tokens to your registered iOS application, with the OAuth PKCE consent flow implemented. With this, you're ready to install the SDK and start accepting payments.