Sandbox
Help

[PREVIEW] Revolut Wallet Loyalty Callbacks2026-09-17

Pre-release preview

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-enrol registers 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 a 403 UI-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) with prompt=create, sends the customer's browser to GET /oauth2/authorize, exchanges the code at POST /oauth2/token, and fetches the membership from GET /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 with prompt=login and 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

Security scheme type:mutualTLS

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

Pre-release preview

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

Pre-release preview

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 the member_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_scope is terminal.
  • 400 / 401: terminal; Revolut stops retrying.
  • 409: the Idempotency-Key was reused with a different request body; Revolut stops retrying.
  • 429 / 503: temporary capacity issue; Revolut retries, honouring Retry-After.

Validate Revolut's credentials (OAuth 2.0 client credentials, optionally hardened with mutual TLS (mTLS)) and reject any unauthenticated request with 401.

Request

Header parameters
Revolut-Api-Version
stringrequired
Idempotency-Key
string(uuid)required
Request-Id
string
Request body
program_id
string

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.

Headers
Request-Id
string
Body
object

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.

member_id
stringrequired
program_id
stringrequired
tier
string
points_balance
object
pass
required
post/loyalty/auto-enrol
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
SecuritySchemaType:oauth2
Flows:clientCredentials

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

SecuritySchemaType:mutualTLS

Mutual 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

Pre-release preview

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 uses S256.
  • 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_supported includes login and create.
  • id_token_signing_alg_values_supported includes RS256, as OpenID Connect Discovery requires. ES256 is also accepted; the algorithm you sign with is agreed when Revolut's client is registered during onboarding.
  • token_endpoint_auth_methods_supported includes the client-authentication method registered for Revolut: client_secret_post, private_key_jwt, tls_client_auth, or self_signed_tls_client_auth. Revolut never uses client_secret_basic.
  • grant_types_supported includes client_credentials if you implement POST /loyalty/auto-enrol or accept OAuth 2.0 on POST /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

Header parameters
Revolut-Api-Version
string
Request-Id
string

Response

Your authorisation server metadata.

Body
object

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.

issuer
string(uri)required
authorization_endpoint
string(uri)required
token_endpoint
string(uri)required
pushed_authorization_request_endpoint
string(uri)required
jwks_uri
string(uri)required
response_types_supported
arrayrequired
subject_types_supported
arrayrequired
id_token_signing_alg_values_supported
arrayrequired
code_challenge_methods_supported
arrayrequired
require_pushed_authorization_requests
booleanrequired
authorization_response_iss_parameter_supported
booleanrequired
prompt_values_supported
arrayrequired
grant_types_supported
arrayrequired
token_endpoint_auth_methods_supported
arrayrequired
token_endpoint_auth_signing_alg_values_supported
array
mtls_endpoint_aliases
object
scopes_supported
array
get/.well-known/openid-configuration
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

Pre-release preview

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: prompt is create and enrolment_claims carries the details the customer agreed to share. Revolut sends it after POST /loyalty/auto-enrol returns a UI-required 403 other than account_exists, or directly when you don't implement auto-enrol.
  • Link an existing account: prompt is login and enrolment_claims is {}, so no personal data is shared. Revolut sends it when the customer starts "Link existing account" in Revolut Wallet, or after POST /loyalty/auto-enrol returns account_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

Header parameters
Revolut-Api-Version
string
Request-Id
string
Request body

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

client_id
stringrequired
client_secret
string
client_assertion_type
string
client_assertion
string
redirect_uri
string(uri)required
response_type
stringrequired
scope
stringrequired
state
stringrequired
nonce
stringrequired
prompt
stringrequired
code_challenge
stringrequired
code_challenge_method
stringrequired
program_id
string
enrolment_claims
string · application/jsonrequired

Response

Authorisation request stored. Return the single-use request_uri and how long it stays valid.

Body
object

Response body for POST /oauth2/par: the single-use request_uri and how long it stays valid.

request_uri
stringrequired
expires_in
integerrequired
post/oauth2/par
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
SecuritySchemaType:mutualTLS

Mutual 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

Pre-release preview

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 with error=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

Query parameters
client_id
stringrequired
request_uri
stringrequired

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:

ErrorDescription
access_deniedThe customer refused to continue or, in the link flow, has no account with you. Revolut ends the flow.
invalid_request_uriUnknown, expired, or already-used request_uri. Sent without state. Revolut shows an error and the customer can start again from Revolut Wallet.
temporarily_unavailableTemporary error; the customer can retry.

This response has no body.

Headers
Location
string(uri)
get/oauth2/authorize
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

No contentThis response does not have a body.

Issue tokens

Pre-release preview

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 the code_challenge stored with the Pushed Authorization Request (PAR), and include an id_token (OpenID Connect).
  • client_credentials: server-to-server access tokens Revolut uses for POST /loyalty/auto-enrol (scope loyalty.enrol) and, if you accept OAuth 2.0 there, POST /loyalty/apply (scope loyalty.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

Header parameters
Revolut-Api-Version
string
Request-Id
string
Request body

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.

grant_type
stringrequired
code
string
redirect_uri
string(uri)
code_verifier
string
scope
string
client_id
stringrequired
client_secret
string
client_assertion_type
string
client_assertion
string

Response

Tokens issued. Which fields appear depends on the grant.

Headers
Cache-Control
string
Body
object

Response body for POST /oauth2/token (RFC 6749). id_token appears only with the authorization_code grant.

access_token
stringrequired
token_type
stringrequired
expires_in
integerrequired
id_token
string
scope
string
post/oauth2/token
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
SecuritySchemaType:mutualTLS

Mutual 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

Pre-release preview

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

Header parameters
Revolut-Api-Version
stringrequired
Request-Id
string

Response

The membership's profile: its identifiers, current loyalty state, and the complete pass envelope.

Headers
Request-Id
string
Body
object

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.

member_id
stringrequired
program_id
stringrequired
tier
string
points_balance
object
pass
required
get/loyalty/profile
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
SecuritySchemaType:http
Scheme:bearer

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

Pre-release preview

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

Pre-release preview

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 the outcome, the points earned, and optionally the new balance (0 points with no_points_earned when the purchase earns no points).
  • 202: transaction not ready yet (settlement lag); Revolut retries, honouring Retry-After.
  • 404: transaction or active membership definitively not found; Revolut stops retrying.
  • 409: the Idempotency-Key was 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, honouring Retry-After.

Validate Revolut's credentials (mutual TLS (mTLS), JSON Web Signature (JWS), or OAuth 2.0) and reject any unauthenticated request with 401.

Request

Header parameters
Revolut-Api-Version
stringrequired
Idempotency-Key
string(uuid)required
Request-Id
string
Request body

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.

member_id
stringrequired
program_id
stringrequired
transaction_context
objectrequired

Response

Loyalty card applied. Returns the outcome, the points the transaction earned, and, optionally, the customer's new balance.

Headers
Request-Id
string
Body
object

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.

points_earned
objectrequired
outcome
stringrequired
points_balance
object
post/loyalty/apply
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
SecuritySchemaType:mutualTLS

Mutual 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).

SecuritySchemaType:apiKey
Header parameter name:Jws-Signature

Message 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 (RS256 or ES256), 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, and crit: ["b64", "iat"].
  • Replay protection. Reject any request where iat differs from your current time by more than 5 minutes, allowing for clock skew.
  • Key selection and rotation. Verify only against the registered key whose kid matches the header. Revolut may register a new key (new kid) and keep the previous key valid for an overlap window before revoking it; accept any registered, unrevoked kid.
  • Integrity scope. The signature covers the request body only. The Idempotency-Key, Request-Id, HTTP method, path, and Revolut-Api-Version headers are not integrity-protected; TLS (optionally mTLS) and the Idempotency-Key semantics cover them.

May be used on its own or together with mTLS.

SecuritySchemaType:oauth2
Flows:clientCredentials

OAuth 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"
  }
}