Sandbox
Help

Get started with Tap to Pay on iPhone

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:

  1. Your backend generates a random code_verifier string.
  2. Your backend sends a derived code_challenge (SHA-256 hash of the verifier) when redirecting the merchant to the consent page.
  3. After the merchant approves, Revolut returns an authorisation code.
  4. Your backend exchanges the authorisation code for tokens, sending the original code_verifier alongside.
  5. 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:

Generate `code_verifier` + `code_challenge` Redirect to `/partner-confirm` with `code_challenge` Open consent page Show consent screen Merchant clicks Authorise Redirect to callback URL with authorisation code Deliver authorisation code Exchange code for tokens (sends `code_verifier`) Hash verifier, compare to challenge Return `access_token` + `refresh_token` Your backend Merchant's browser Revolut

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.

ComponentResponsibility
Your backendStores 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 appRequests 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 SDKUses 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_token and refresh_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 typeLifetimeRefreshableStored on
access_token~30 minutesYes - use refresh_token to obtain a new oneBackend (backend-led) / iOS app keychain (app-led)
refresh_token7 daysN/ABackend (backend-led) / iOS app keychain (app-led)
Device token~30 minutesNo - request a new one from your backendiOS 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 typeDescriptionUsed in
authorization_codeExchanges the authorisation code for an access_token and refresh_token.Step 2.4
refresh_tokenRefreshes the tokens and returns new ones when the access_token expires.Refresh the access token
exchange_tokenExchanges 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:

  1. Register your application in the Developer Portal, enable the Tap to Pay scope, set up sandbox authentication, and submit to production.
  2. 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.
  3. 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

  1. Register your application
  2. Get merchant consent
  3. Get a device token

Before you begin

Before you start, ensure you have the following:

  • Revolut Business account with Merchant features enabled.
  • API & integrations permission 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

  1. Log in to the Developer Portal.
  2. Click Add new to create a new application.
  3. Give a name to the application. You may also choose to upload an application icon.

Don't have a Developer account yet?

See Sign up for a developer account

1.2 Enable the Tap to Pay scope

  1. Go to Settings > Scopes.
  2. Enable Accept Payments with Tap to Pay on iPhone through a 3rd party application.
  3. 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.

  1. Configure the Redirect URLs for the OAuth flow.

  2. Configure one or more allowed Bundle IDs for the iOS apps embedding the SDK.

  3. 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 der
    ParameterDescriptionExample
    CTwo-letter country codeGB
    STState or provinceEngland
    LLocality (city)London
    OYour application or organisation nameMy Taxi App
    OUOrganisation Unit ID - provided by Revolut in the Developer Portal001580000103UAvAAM
    CNCommon Name - provided by Revolut in the Developer Portal2kiXQyo0tedjW2somjSgH7

    You can copy the full openssl command with the OU and CN values pre-filled from the Sandbox authentication tab in the Developer Portal.

    For OBIE/eIDAS certificate alternatives, see Certificate types.

  4. 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

  1. Click the Submit to production widget. The scopes you enabled earlier are already loaded.
  2. Configure the Production endpoints > Redirect URLs.
  3. Configure the iOS Applications > Bundle IDs for production.
  4. Upload a production certificate in the same format as the sandbox CSR (or OBIE/eIDAS certificate).
  5. 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.

Bundle ID validation

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.

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:

ValueDescriptionUsed in
code_verifierA 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_challengeThe 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.

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.

Consent request - browser redirect
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=xyz123

The request consists of the consent endpoint and the following query parameters:

EnvironmentBase URL
Productionhttps://business.revolut.com/partner-confirm
ParameterValueDescription
response_typecodeFixed value
scopetap_to_pay_sdkFixed 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_methodS256Fixed 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:

Redirect URL
GET https://yourapp.com/callback?code={authorisation_code}&state={state}
ParameterDescription
codeThe authorisation code (one-time use, short-lived). Use this to exchange for tokens in Step 2.4.
stateMust 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.

EnvironmentToken endpoint
Productionhttps://merchant-secure.revolut.com/api/oauth/token
Example request
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:

ParameterValueDescription
client_id{your_client_id}Same client ID from Step 1
grant_typeauthorization_codeFixed 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.

EnvironmentToken endpoint
Productionhttps://merchant-secure.revolut.com/api/oauth/token
Example request
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_veKzTCONV8TKU0qOrahK4peTQt15PzkEPGTmeHBfQSY

Form parameters:

ParameterValueDescription
client_id{your_client_id}Same client ID from Step 1
grant_typeexchange_tokenFixed 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.

EnvironmentToken endpoint
Productionhttps://merchant-secure.revolut.com/api/oauth/token
Example request
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:

ParameterValueDescription
client_id{your_client_id}Same client ID from Step 1
grant_typerefresh_tokenFixed value - triggers the refresh flow
refresh_token{refresh_token}The refresh_token obtained in Step 2.4

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.
  • code_verifier and code_challenge generation implemented on backend.
  • Consent URL constructed with all required query parameters.
  • Redirect handler captures code and state from the callback.
  • state parameter verified against the value sent in the consent request.
  • Authorisation code exchanged for access_token and refresh_token.
  • code_verifier sent in the exchange (not the hash).
  • access_token and refresh_token stored securely on backend (never exposed to device).

Device token

  • Token exchange endpoint implemented (grant_type=exchange_token).
  • subject_token uses the access_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_token from response stored immediately (replaces the old one).
  • Expired access_token replaced 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.


What's next

Integration guide
Step-by-step iOS SDK integration
SDK reference
Methods and parameters for the Tap to Pay iOS SDK
FAQs
Common questions about Tap to Pay on iPhone
Rate this page