This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Loyalty endpoints you host and Revolut calls. Implement them to this contract so customers can enrol in your loyalty program from Revolut Wallet, and so their loyalty cards are applied automatically when they pay with a Revolut card. Revolut is always the client; you are the server.
- Enrol a customer automatically (optional):
POST /loyalty/auto-enrolregisters the customer without any customer interaction, using the details they agreed to share: their name, email, and phone number, verified by Revolut's know your customer (KYC) checks, plus any partner-configured fields they enter. If the customer must act in a browser (accept your terms, provide missing details), return a403UI-required code and Revolut falls back to the browser flow. If you don't implement auto-enrol, Revolut starts every new enrolment with the browser flow. - Enrol through the browser flow on your authorisation server: Revolut reads your
OpenID Connect discovery document, pushes the authorisation request (
POST /oauth2/par) withprompt=create, sends the customer's browser toGET /oauth2/authorize, exchanges the code atPOST /oauth2/token, and fetches the membership fromGET /loyalty/profile. - Link a customer's existing account: when the customer starts "Link existing account" in
Revolut Wallet, or auto-enrol returns
account_exists, Revolut runs the same browser flow withprompt=loginand shares no personal data: the customer signs in to you and consents to link their membership. - Apply a loyalty card to a transaction:
POST /loyalty/apply. After a customer enrolled in your loyalty program pays with a Revolut card at your business, Revolut asks you to apply their loyalty card and report the points earned.
Passes themselves are managed through the companion APIs: the Revolut-hosted Wallet Partner API and the pass endpoints you host in the Wallet Partner Callbacks API.
Where the endpoints live. POST /loyalty/apply, POST /loyalty/auto-enrol, and
GET /loyalty/profile live under the callback base URL you register during onboarding. The
authorisation-server endpoints live under the issuer URL you register, which may include a
path: Revolut reads {issuer}/.well-known/openid-configuration and calls the endpoint URLs it
lists, so the /oauth2/* paths in this reference are illustrative. All servers URLs are
examples.
Start with the Revolut Wallet guide and Get started to onboard your business.
Authentication
mutualTLSMutual TLS (mTLS). Revolut presents a client certificate signed by the Revolut Partner CA. Validate it against the Revolut CA chain from onboarding and reject any request without a valid Revolut certificate. Your Partner ID is bound to it. Can be used on its own, or layered on top of JSON Web Signature (JWS) signing for defence in depth.
A failed mTLS handshake terminates at the TLS layer and produces no HTTP response.
Return 401 only when the connection was established but the client certificate is
missing, untrusted, or not bound to your Partner ID (for example, when TLS
terminates at a proxy in front of your application).
Loyalty enrolment
Enrol customers in your loyalty program from Revolut Wallet, or link a membership they
already have with you: automatic enrolment, the OAuth 2.0 browser flow on your
authorisation server, and the loyalty profile Revolut reads when the flow completes. Both
enrolment and linking record the partner-issued member_id and program_id, which Revolut
later sends on POST /loyalty/apply.
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Enrol a customer automatically
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Revolut calls this when a customer enrols in your loyalty program from Revolut Wallet, to register them without any customer interaction. The request carries the details the customer agreed to share: their name, email, and phone number, verified by Revolut's know your customer (KYC) checks, plus any partner-configured fields they enter. Create the membership and return its identifiers plus the complete loyalty pass envelope.
This endpoint is optional: tell the Revolut Wallet team during onboarding whether you
implement it. If you don't, Revolut starts every new enrolment with the OAuth 2.0 browser
flow (POST /oauth2/par with prompt=create).
If the customer must act in a browser (accept your terms, provide missing data), return
403 with the matching UI-required code below and Revolut falls back to the OAuth 2.0
browser flow. Revolut does not retry the auto-enrol after a 403.
Revolut does not call this endpoint when the customer starts "Link existing account" in
Revolut Wallet: that flow goes straight to the OAuth 2.0 browser flow with prompt=login
and no enrolment claims.
Never silently link an existing membership: if the shared data matches an existing
account, return 403 with account_exists. Revolut then runs the link flow, where the
customer signs in to you and consents to link their membership. Never create a duplicate
membership.
Never succeed silently when the customer must accept terms or complete a step: return
the matching 403 code instead.
Idempotency (this operation has side effects on your side): Revolut sends one
Idempotency-Key for each logical enrolment and reuses it for every retry. If the
enrolment has already completed, return the original 200 without creating another
membership. Reusing the same key with a different request body returns 409. Do not
store 429 or 503 as the final result for a key; Revolut retries them with the
same key. Retain completed keys and their responses for at least 7 days.
Status semantics (these drive Revolut's retry behaviour):
200: membership created; return themember_id,program_id, and the complete pass envelope.403: UI-required (see the codes below); Revolut never retries and falls back to the OAuth 2.0 browser flow.insufficient_scopeis terminal.400/401: terminal; Revolut stops retrying.409: theIdempotency-Keywas reused with a different request body; Revolut stops retrying.429/503: temporary capacity issue; Revolut retries, honouringRetry-After.
Validate Revolut's credentials (OAuth 2.0 client credentials, optionally hardened with mutual TLS (mTLS)) and reject any unauthenticated request with 401.
Request
Request body for POST /loyalty/auto-enrol: the enrolment claims the customer agreed
to share, plus the loyalty program to enrol them in when you operate several. At least
one enrolment claim is always present.
Response
Membership created. Return the identifiers of the new membership and the complete loyalty pass envelope for the customer's Revolut Wallet.
Result of an automatic enrolment: the identifiers of the membership you created, plus the complete loyalty pass envelope for the customer's Revolut Wallet. Revolut ignores unknown fields in this response, so you may add fields without breaking compatibility.
curl -X POST "https://passes.partner.example/revolut-wallet/loyalty/auto-enrol" \
-H "Authorization: Bearer <yourAccessToken>" \
-H "Content-Type: application/json" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9" \
-d '{
"given_name": "Alan",
"family_name": "Maxwell",
"email": "alan.maxwell@example.com",
"phone_number": "+447700900123",
"custom_fields": {
"vehicle_type": "car",
"licence_plate": "BX21 ABC"
},
"program_id": "program_gb_rewards"
}'Authorization
oauth2clientCredentialsOAuth 2.0 client credentials. Revolut requests a short-lived access token from your
token endpoint, the token_endpoint in your discovery document (authenticating with its
onboarding credential: client_secret, private_key_jwt, or its mTLS certificate), and
sends it as Authorization: Bearer <token>. The token carries the scopes Revolut
requests (loyalty.apply, loyalty.enrol). May be used on its own or together with
mTLS.
mutualTLSMutual TLS (mTLS). Revolut presents a client certificate signed by the Revolut Partner CA. Validate it against the Revolut CA chain from onboarding and reject any request without a valid Revolut certificate. Your Partner ID is bound to it. Can be used on its own, or layered on top of JSON Web Signature (JWS) signing for defence in depth.
A failed mTLS handshake terminates at the TLS layer and produces no HTTP response.
Return 401 only when the connection was established but the client certificate is
missing, untrusted, or not bound to your Partner ID (for example, when TLS
terminates at a proxy in front of your application).
Request body samples
{
"given_name": "Alan",
"family_name": "Maxwell",
"email": "alan.maxwell@example.com",
"phone_number": "+447700900123",
"custom_fields": {
"vehicle_type": "car",
"licence_plate": "BX21 ABC"
},
"program_id": "program_gb_rewards"
}Response body samples
{
"member_id": "9988776655",
"program_id": "program_gb_rewards",
"tier": "Gold",
"points_balance": {
"value": "4820",
"unit": "points"
},
"pass": {
"format": "revpass",
"partner_reference": "brightpoints-9988776655",
"version": 1,
"rev_pass": {
"type": "loyalty_pass",
"semantics": {
"member": {
"full_name": "Alan Maxwell",
"member_id": "9988776655",
"member_since_on": "2026-09-17"
},
"loyalty": {
"program_name": "BrightPoints",
"tier": "Gold",
"points_balance": {
"value": "4820",
"unit": "points"
}
}
}
}
}
}Retrieve authorisation server metadata
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Your OpenID Connect discovery document (OpenID Connect Discovery 1.0). Revolut reads it
from {issuer}/.well-known/openid-configuration, using the issuer URL you register
during onboarding, to find your Pushed Authorization Request (PAR), authorisation, and
token endpoints and the keys that sign your ID tokens. Revolut then calls the URLs the
document lists. Serve it publicly over HTTPS; it carries no secrets.
Your document can list more values than this reference requires, for example extra grant
types or prompt values. Revolut relies on the following:
- PAR is mandatory (
require_pushed_authorization_requests: true) and PKCE usesS256. - Authorisation responses carry
iss(RFC 9207), so Revolut can tell your authorisation server apart from other partners' servers that share its redirect URI. prompt_values_supportedincludesloginandcreate.id_token_signing_alg_values_supportedincludesRS256, as OpenID Connect Discovery requires.ES256is also accepted; the algorithm you sign with is agreed when Revolut's client is registered during onboarding.token_endpoint_auth_methods_supportedincludes the client-authentication method registered for Revolut:client_secret_post,private_key_jwt,tls_client_auth, orself_signed_tls_client_auth. Revolut never usesclient_secret_basic.grant_types_supportedincludesclient_credentialsif you implementPOST /loyalty/auto-enrolor accept OAuth 2.0 onPOST /loyalty/apply.
Keep the previous signing key in your JSON Web Key Set (JWKS) while you rotate keys, so ID tokens signed with it still verify.
Request
Response
Your authorisation server metadata.
Your authorisation server metadata (OpenID Connect Discovery 1.0, with the fields that RFC 8414, RFC 9126, and RFC 9207 add). The arrays must include the values described below; extra values and extra fields are allowed.
curl -X GET "https://passes.partner.example/revolut-wallet/.well-known/openid-configuration" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9"Response body samples
{
"issuer": "https://auth.partner.example",
"authorization_endpoint": "https://auth.partner.example/oauth2/authorize",
"token_endpoint": "https://auth.partner.example/oauth2/token",
"pushed_authorization_request_endpoint": "https://auth.partner.example/oauth2/par",
"jwks_uri": "https://auth.partner.example/.well-known/jwks.json",
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256",
"ES256"
],
"code_challenge_methods_supported": [
"S256"
],
"require_pushed_authorization_requests": true,
"authorization_response_iss_parameter_supported": true,
"prompt_values_supported": [
"none",
"login",
"consent",
"create"
],
"grant_types_supported": [
"authorization_code",
"client_credentials"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"private_key_jwt"
],
"token_endpoint_auth_signing_alg_values_supported": [
"RS256",
"ES256"
],
"scopes_supported": [
"openid",
"loyalty.profile",
"loyalty.enrol",
"loyalty.apply"
]
}Push an authorisation request
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
OAuth 2.0 Pushed Authorization Request (PAR) endpoint (RFC 9126): the
pushed_authorization_request_endpoint in your discovery document. Revolut's backend calls
it before sending the customer's browser to your authorisation endpoint, so the enrolment
claims travel over a server-to-server channel instead of through the browser. Store the
request and return a single-use request_uri.
Revolut pushes one of the following variants:
- Enrol a new member:
promptiscreateandenrolment_claimscarries the details the customer agreed to share. Revolut sends it afterPOST /loyalty/auto-enrolreturns a UI-required403other thanaccount_exists, or directly when you don't implement auto-enrol. - Link an existing account:
promptisloginandenrolment_claimsis{}, so no personal data is shared. Revolut sends it when the customer starts "Link existing account" in Revolut Wallet, or afterPOST /loyalty/auto-enrolreturnsaccount_exists.
Both variants carry Proof Key for Code Exchange (PKCE, RFC 7636): store code_challenge
with the request and verify the code_verifier Revolut sends to your token endpoint.
Both also carry program_id when you operate several loyalty programs.
Authenticate Revolut's client credentials (client secret, private key JSON Web Token
(JWT), or mutual TLS (mTLS), as registered during onboarding) and reject any
unauthenticated request with 401 (invalid_client). Unless mTLS is the registered
method, Revolut sends a client secret or JWT assertion in the request body; that's why
security lists mTLS as optional.
The request_uri must expire after expires_in seconds (60–90) and be consumable
exactly once at your authorisation endpoint.
Request
Form body for POST /oauth2/par (application/x-www-form-urlencoded), per the Pushed
Authorization Request (PAR) flow (RFC 9126). Carries the OAuth 2.0 authorisation request
parameters, Proof Key for Code Exchange (PKCE, RFC 7636), and the enrolment claims:
enrolment requests carry the customer's claims, and link requests carry none (see
enrolment_claims).
Response
Authorisation request stored. Return the single-use request_uri and how long it
stays valid.
Response body for POST /oauth2/par: the single-use request_uri and how long it
stays valid.
curl -X POST "https://passes.partner.example/revolut-wallet/oauth2/par" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9"Authorization
mutualTLSMutual TLS (mTLS). Revolut presents a client certificate signed by the Revolut Partner CA. Validate it against the Revolut CA chain from onboarding and reject any request without a valid Revolut certificate. Your Partner ID is bound to it. Can be used on its own, or layered on top of JSON Web Signature (JWS) signing for defence in depth.
A failed mTLS handshake terminates at the TLS layer and produces no HTTP response.
Return 401 only when the connection was established but the client certificate is
missing, untrusted, or not bound to your Partner ID (for example, when TLS
terminates at a proxy in front of your application).
Request body samples
{
"client_id": "revolut-wallet",
"client_secret": "9f8e7d6c-5b4a-4f3e-2d1c-0b9a8f7e6d5c",
"redirect_uri": "https://wallet.revolut.com/loyalty/oauth2/callback",
"response_type": "code",
"scope": "openid loyalty.profile",
"state": "KhB6vF3nY2zQ9wX1",
"nonce": "a7b8c9d0e1f2",
"prompt": "create",
"code_challenge": "6jX6L4rvyuv0fbu5qAL_4fN1-0t9-YwmTvYH0fjX-w4",
"code_challenge_method": "S256",
"program_id": "program_gb_rewards",
"enrolment_claims": "{\"given_name\":\"Alan\",\"family_name\":\"Maxwell\",\"email\":\"alan.maxwell@example.com\",\"phone_number\":\"+447700900123\",\"custom_fields\":{\"vehicle_type\":\"car\",\"licence_plate\":\"BX21 ABC\"}}"
}Response body samples
{
"request_uri": "urn:ietf:params:oauth:request_uri:8b2f9a1c-6d3e-4f7a-9c5b-1e2d3f4a5b6c",
"expires_in": 90
}Authorise the customer in the browser
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Your authorisation endpoint: the authorization_endpoint in your discovery document. The
customer's browser (a secure system browser, never an embedded web view) opens it with
the request_uri Revolut pushed at POST /oauth2/par. Look up the stored request, check
that client_id matches the client the request_uri was issued to (RFC 9126), and route
the customer by the stored prompt value. Route only on prompt, never on whether
enrolment_claims is empty:
prompt=login(link an existing account): route the customer straight to your sign-in screen and force re-authentication, ignoring any existing session. After the customer signs in, ask for consent to link their loyalty membership. If the customer has no account with you, redirect back witherror=access_denied.prompt=create(enrol a new member): pre-fill your registration form with the pushed enrolment claims and let the customer register. If the pushed data matches an existing membership, offer sign-in and link the account; never create a duplicate.
Show your Terms and Conditions and privacy policy and collect any consent you require.
On completion, redirect back (HTTP 302) to the redirect_uri from the stored PAR,
appending code, state, and iss on success, or error, state, and iss on
failure. iss is your issuer URL (RFC 9207): Revolut rejects a response whose iss
doesn't match the issuer it started the flow with. Bind the membership server-side to
the authorisation code; never put personal data or member_id in the redirect URL.
If the request_uri is unknown, expired, or already used, there is no stored request and
no state to return: redirect to the redirect URI registered for client_id with
error=invalid_request_uri and iss. If no valid redirect URI is known for the
client_id, render an error page instead of redirecting.
Reject authorisation requests that bypass request_uri (raw OAuth parameters) with 400,
and render an error page for the customer.
As a browser-facing endpoint, this operation carries no credentials, correlation ID
(Request-Id), or API-version header.
Request
Response
Redirect back to Revolut, with iss percent-encoded in the query string. Success:
Location: {redirect_uri}?code={authorization_code}&state={state}&iss={issuer}.
Failure: Location: {redirect_uri}?error={error}&state={state}&iss={issuer} with one
of the following errors:
| Error | Description |
|---|---|
access_denied | The customer refused to continue or, in the link flow, has no account with you. Revolut ends the flow. |
invalid_request_uri | Unknown, expired, or already-used request_uri. Sent without state. Revolut shows an error and the customer can start again from Revolut Wallet. |
temporarily_unavailable | Temporary error; the customer can retry. |
This response has no body.
curl -X GET "https://passes.partner.example/revolut-wallet/oauth2/authorize?client_id=revolut-wallet&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A8b2f9a1c-6d3e-4f7a-9c5b-1e2d3f4a5b6c"Response body samples
Issue tokens
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
OAuth 2.0 token endpoint (RFC 6749): the token_endpoint in your discovery document.
Revolut's backend calls it server-to-server for the following grants:
authorization_code: exchange the code from the browser redirect, together with the Proof Key for Code Exchange (PKCE)code_verifier(RFC 7636). Check that the verifier matches thecode_challengestored with the Pushed Authorization Request (PAR), and include anid_token(OpenID Connect).client_credentials: server-to-server access tokens Revolut uses forPOST /loyalty/auto-enrol(scopeloyalty.enrol) and, if you accept OAuth 2.0 there,POST /loyalty/apply(scopeloyalty.apply).
ID token. A JSON Web Token (JWT) signed with a key published at your jwks_uri, using
the algorithm agreed during onboarding (RS256 or ES256). Include the following
claims: iss (your issuer URL), sub (your stable identifier for the customer), aud
(Revolut's client_id), exp, iat, nonce (matching the PAR), and member_id (the
membership the customer enrolled in or linked). Revolut validates the signature, iss,
aud, exp, and nonce, and ends the flow if any check fails.
Authenticate Revolut on every grant (client secret, private key JWT, or mutual TLS
(mTLS), as registered during onboarding) and reject any unauthenticated request with
401 (invalid_client). Unless mTLS is the registered method, Revolut sends a client
secret or JWT assertion in the request body; that's why security lists mTLS as
optional.
Authorisation codes are single-use and valid for 5 minutes. Reject a replayed code, or a
code_verifier that doesn't match, with 400 (invalid_grant). Don't consume the code
when you return 429 or 503: Revolut retries the exchange with the same code. Return
every token response with Cache-Control: no-store (RFC 6749, section 5.1).
Request
Form body for POST /oauth2/token (application/x-www-form-urlencoded), per the
OAuth 2.0 token endpoint (RFC 6749). Which fields apply depends on the grant and on the
client-authentication method registered during onboarding.
Response
Tokens issued. Which fields appear depends on the grant.
Response body for POST /oauth2/token (RFC 6749). id_token appears only with the
authorization_code grant.
curl -X POST "https://passes.partner.example/revolut-wallet/oauth2/token" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9"Authorization
mutualTLSMutual TLS (mTLS). Revolut presents a client certificate signed by the Revolut Partner CA. Validate it against the Revolut CA chain from onboarding and reject any request without a valid Revolut certificate. Your Partner ID is bound to it. Can be used on its own, or layered on top of JSON Web Signature (JWS) signing for defence in depth.
A failed mTLS handshake terminates at the TLS layer and produces no HTTP response.
Return 401 only when the connection was established but the client certificate is
missing, untrusted, or not bound to your Partner ID (for example, when TLS
terminates at a proxy in front of your application).
Request body samples
{
"grant_type": "authorization_code",
"code": "SplxlOBeZQQYbYSaW6S5bA",
"redirect_uri": "https://wallet.revolut.com/loyalty/oauth2/callback",
"code_verifier": "s6GMlOmVYBylONZVcuAyzNpBjdDeZEYanMCV9NVy4wM-kQ9",
"client_id": "revolut-wallet",
"client_secret": "9f8e7d6c-5b4a-4f3e-2d1c-0b9a8f7e6d5c"
}Response body samples
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 300,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InBhcnRuZXItc2lnbmluZy0yMDI2LTA5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2F1dGgucGFydG5lci5leGFtcGxlIiwic3ViIjoiY3VzdF80ZjJhOWMxZSIsImF1ZCI6InJldm9sdXQtd2FsbGV0IiwiZXhwIjoxNzg5NjQ3MzAwLCJpYXQiOjE3ODk2NDY3MDAsIm5vbmNlIjoiYTdiOGM5ZDBlMWYyIiwibWVtYmVyX2lkIjoiOTk4ODc3NjY1NSJ9.lQp5M4HH0GLF09AYM3sPOgw9OFk7XzWGaDjrPXoVi1ZSPGPkjU_Xnag-82kvnyGhGOLKRRROZ1Dk1FJ3LxUi-XkFXOXfibT-xhOoiPICHH4aufCLZVVLcjZ3NB30eztY5VNvoAsz1qW7ec2gSkQDV76GKASxs3RggSNW4PHxl7Cqv4vJhUfyOkFo64HaXvWCQ5fTpuYTym8p9IXMEFTLmf8GH4zZ7xSnNLDIepZjJejS1JXa9M1ufC2Ld_WnDw2-Pq6T3su5iKPnsNlxXtwEjBJn_M2BMF0H-noK4b48Zv39cQd-BXhrc_crbkwFgzY0ekkJmUMvMTS2oOWDZB0_cA",
"scope": "openid loyalty.profile"
}Retrieve a loyalty profile
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Revolut's backend calls this once per enrolment or link, immediately after exchanging the
authorisation code at your token endpoint, to fetch the membership the customer just
enrolled in or linked. Return the membership identifiers, its current loyalty state, and
the complete pass envelope for the customer's Revolut Wallet. The member_id you return
must equal the member_id claim of the ID token you issued for the same code.
After this call, keep the pass current through pass updates: push changes through the Wallet Partner API, or serve Revolut's poll requests.
Validate the access token your authorisation server issued and require the
loyalty.profile scope. Revolut uses the token only for this endpoint, including any
retries after 429 or 503, so issue a short-lived access token whose lifetime still
covers the Retry-After delays you return. Signal token errors in the WWW-Authenticate
header (RFC 6750): reject a request without a token with 401 and a bare Bearer
challenge, an expired or invalid token with 401 (invalid_token), and a token without
loyalty.profile with 403 (insufficient_scope).
When the customer unlinks the membership in Revolut Wallet, Revolut doesn't notify you; it stops calling your loyalty endpoints for that membership.
Request
Response
The membership's profile: its identifiers, current loyalty state, and the complete pass envelope.
Response body for GET /loyalty/profile: the identifiers of the membership the customer
enrolled in or linked, its current loyalty state, and the complete pass envelope.
Revolut ignores unknown fields in this response, so you may add fields without breaking
compatibility.
curl -X GET "https://passes.partner.example/revolut-wallet/loyalty/profile" \
-H "Authorization: Bearer <yourAccessToken>" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9"Authorization
httpbearerAccess token issued by your authorisation server at its token endpoint for the
customer's enrolment or link. It must carry the loyalty.profile scope and is sent as
Authorization: Bearer <token>.
Response body samples
{
"member_id": "9988776655",
"program_id": "program_gb_rewards",
"tier": "Gold",
"points_balance": {
"value": "4820",
"unit": "points"
},
"pass": {
"format": "revpass",
"partner_reference": "brightpoints-9988776655",
"version": 1,
"template_id": "3f6c2a9e-8b71-4d0c-9e52-7a1b4c8d2e60",
"rev_pass": {
"type": "loyalty_pass",
"semantics": {
"member": {
"full_name": "Alan Maxwell",
"member_id": "9988776655",
"member_since_on": "2026-09-17"
},
"loyalty": {
"program_name": "BrightPoints",
"tier": "Gold",
"points_balance": {
"value": "4820",
"unit": "points"
}
}
}
}
}
}Loyalty auto-apply
Auto-apply loyalty cards to Revolut-card transactions. Revolut sends the partner-issued membership and program identifiers recorded during enrolment or linking, together with the transaction context you use to match the purchase. Revolut uses the merchant ID and acquiring institution ID you provide during onboarding to route eligible transactions. The acquiring institution ID is routing data and is not included in the callback request.
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Apply a loyalty card to a transaction
This is a pre-release preview of upcoming documentation. The features, workflows, and UI described here are subject to change before the final release.
Revolut calls this after detecting an eligible Revolut-card transaction by a customer enrolled
in your loyalty program. Locate the active membership using member_id and program_id, match
the purchase from transaction_context, apply the loyalty card, and award the points earned.
Transaction matching: rrn + merchant_id is the primary key; once you have a
candidate, validate transaction_at (±24 hours) and require total.currency and the decimal
numeric value of total.amount to match exactly. The LoyaltyTransactionContext schema
documents the authoritative algorithm and collision handling.
If member_id and program_id do not identify an active membership, return 404 with
membership_not_found. If the membership is active but the purchase earns no points under
your program rules, return 200 with outcome set to no_points_earned and 0 points.
Idempotency (this operation has side effects on your side): Revolut sends one
Idempotency-Key for each logical apply operation and reuses it for every retry. If the
operation has already completed, return the original successful result without awarding
points again. Reusing the same key with a different request body returns 409. Do not store
202, 429, or 503 as the final result for a key; Revolut retries them with the same key.
Status semantics (these drive Revolut's retry behaviour):
200: loyalty card applied; return theoutcome, the points earned, and optionally the new balance (0points withno_points_earnedwhen the purchase earns no points).202: transaction not ready yet (settlement lag); Revolut retries, honouringRetry-After.404: transaction or active membership definitively not found; Revolut stops retrying.409: theIdempotency-Keywas reused with a different request body, or the transaction match is ambiguous (ambiguous_transaction); Revolut stops retrying.410: transaction existed but is void (refund/cancellation); Revolut stops.429/503: temporary capacity issue; Revolut retries, honouringRetry-After.
Validate Revolut's credentials (mutual TLS (mTLS), JSON Web Signature (JWS), or OAuth 2.0) and reject any unauthenticated request with 401.
Request
Request body for POST /loyalty/apply. Contains the partner-issued membership and program
identifiers recorded during enrolment or linking, plus the transaction context to match.
Response
Loyalty card applied. Returns the outcome, the points the transaction earned, and,
optionally, the customer's new balance.
Result of applying a loyalty card to a transaction: the outcome, the points the
purchase earned, and optionally the customer's new balance. Revolut ignores unknown
fields in this response, so you may add fields without breaking compatibility.
curl -X POST "https://passes.partner.example/revolut-wallet/loyalty/apply" \
-H "Content-Type: application/json" \
-H "Revolut-Api-Version: 2026-09-17" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9" \
-d '{
"member_id": "member_987654",
"program_id": "program_gb_rewards",
"transaction_context": {
"rrn": "123456789012",
"merchant_id": "mer_example_01",
"transaction_at": "2026-09-17T12:00:00.000Z",
"total": {
"amount": "72.40",
"currency": "GBP"
}
}
}'Authorization
mutualTLSMutual TLS (mTLS). Revolut presents a client certificate signed by the Revolut Partner CA. Validate it against the Revolut CA chain from onboarding and reject any request without a valid Revolut certificate. Your Partner ID is bound to it. Can be used on its own, or layered on top of JSON Web Signature (JWS) signing for defence in depth.
A failed mTLS handshake terminates at the TLS layer and produces no HTTP response.
Return 401 only when the connection was established but the client certificate is
missing, untrusted, or not bound to your Partner ID (for example, when TLS
terminates at a proxy in front of your application).
apiKeyJws-SignatureMessage signing (JSON Web Signature, JWS). Revolut signs every request as a
detached JWS (RFC 7515, Appendix F) and sends the compact serialisation in the
Jws-Signature header.
Verify it exactly as follows:
- Signed bytes. The signature covers the exact raw HTTP request body bytes as
transmitted; nothing is re-serialised or re-encoded. The JWS uses
b64: false(RFC 7797), so to verify, take the body bytes you received, verbatim, as the JWS payload. - Protected JOSE header.
alg(RS256orES256),kid(the ID of the Revolut Partner signing key whose certificate you registered during onboarding),iat(integer Unix epoch seconds, UTC, when the signature was created),b64: false, andcrit: ["b64", "iat"]. - Replay protection. Reject any request where
iatdiffers from your current time by more than 5 minutes, allowing for clock skew. - Key selection and rotation. Verify only against the registered key whose
kidmatches the header. Revolut may register a new key (newkid) and keep the previous key valid for an overlap window before revoking it; accept any registered, unrevokedkid. - Integrity scope. The signature covers the request body only. The
Idempotency-Key,Request-Id, HTTP method, path, andRevolut-Api-Versionheaders are not integrity-protected; TLS (optionally mTLS) and theIdempotency-Keysemantics cover them.
May be used on its own or together with mTLS.
oauth2clientCredentialsOAuth 2.0 client credentials. Revolut requests a short-lived access token from your
token endpoint, the token_endpoint in your discovery document (authenticating with its
onboarding credential: client_secret, private_key_jwt, or its mTLS certificate), and
sends it as Authorization: Bearer <token>. The token carries the scopes Revolut
requests (loyalty.apply, loyalty.enrol). May be used on its own or together with
mTLS.
Request body samples
{
"member_id": "member_987654",
"program_id": "program_gb_rewards",
"transaction_context": {
"rrn": "123456789012",
"merchant_id": "mer_example_01",
"transaction_at": "2026-09-17T12:00:00.000Z",
"total": {
"amount": "72.40",
"currency": "GBP"
}
}
}Response body samples
{
"outcome": "applied",
"points_earned": {
"value": "72",
"unit": "points"
},
"points_balance": {
"value": "4892",
"unit": "points"
}
}