Keep passes current after creation using push updates or poll-based refreshes.
When details change after creating a pass - a gate change, a seat update, loyalty points earned - customers need the updated pass in their wallet without re-adding it.
Two channels keep passes in sync: push the full updated pass via the Partner API, or let Revolut poll your endpoint for the latest state. You can use either channel on its own, or combine them - poll for routine refreshes and push for time-sensitive changes. Bump version on every change regardless of which method you use.
Push vs. poll
Most integrations start with push - you call Revolut's API from your backend, so there's no server endpoint to host, and updates reach wallets in near real-time. Poll becomes useful when Revolut needs to refresh passes independently (for example, after an app relaunch or a sync cycle), or when you don't want to push on every minor change - but it requires you to host a GET endpoint that Revolut can call. You can combine both approaches: poll for routine refreshes and push for urgent changes.
| Push | Poll | |
|---|---|---|
| Direction | You call Revolut | Revolut calls you |
| Latency | Near real-time | Scheduled (minutes) |
| You implement | PUT calls with Idempotency-Key and version bump | A GET endpoint with ETag and 304 support |
| Best for | Time-sensitive updates (gate change, seat change) | Steady-state or infrequent updates |
How it works
Like pass creation, this is a server-to-server integration - the customer never interacts with the API directly. Your backend and Revolut's servers exchange the full pass body; Revolut renders the updated pass in the customer's wallet.
Pass updates use two channels: push and poll. The only difference is the direction of the calls - during push, your backend calls the Revolut-hosted Partner API to replace the stored pass. During polling, Revolut calls your Wallet Partner Callbacks endpoint to fetch the latest state. Both channels send the same full pass body - there is no partial update.
The following components interact in this flow:
| Component | Role in updates |
|---|---|
Partner API PUT /passes/{id} | Push channel - your backend calls Revolut with the full updated pass |
Wallet Partner Callbacks GET /passes/{partner_reference} | Poll channel - Revolut calls your endpoint to fetch the latest pass |
version | Monotonically increasing version - bump on every change, so Revolut can detect stale updates |
ETag / If-None-Match | Conditional request headers for poll - save bandwidth when the pass is unchanged |
ref: prefix | Addressing scheme for auto-provisioned passes where Revolut never returned a pass id |
Both channels use the same pass body: the full semantic content for the pass type, without template_id (which is immutable) - partial update is not supported. Revolut replaces the stored pass and fans it out to every holder. Because a pass can be held by multiple customers (e.g. a loyalty card shared across a team), Revolut pushes every update to every wallet that holds it - you only send the update once.
Bump version whenever the pass content actually changes (new gate, updated seat, points balance updated). Sending the same body with the same version is a no-op; sending a different body with a stale version returns 409.
Update flow
A pass detail changes in your system (gate change, seat update, points balance updated). The two channels handle this independently:
- Your backend calls
PUT /passes/{id}with the full updated pass body and a bumpedversion. - Revolut validates the update, replaces the stored pass, and fans it out to every wallet that holds it.
- Revolut renders the updated pass in the customer's wallet.
Before you begin
- Complete Get started to get your Partner ID, API credentials, and
template_id - Have at least one pass created via Create a pass
Implement pass updates
This section covers the request/response details for each channel, including the response codes your poll endpoint must implement. For the full error reference, see the Partner API and Wallet Partner Callbacks.
1. Push updates via the Partner API
To push updates, your backend calls the Revolut-hosted Partner API. Send a request to PUT /passes/{id} with the complete, updated pass. This request executes a full replacement - send every field, including ones that haven't changed. Omit template_id; it was set at creation and is immutable.
PUT https://partner.wallet.revolut.com/passes/{id}
Revolut-Api-Version: 2026-07-01
Idempotency-Key: {uuid}
Content-Type: application/json
{
"partner_reference": "skyjet-2241-R7K92M",
"type": "boarding_pass",
"version": 2,
"semantics": {
"passenger": { "full_name": "Sarah Connor" },
"flight": {
"flight_number": "2241",
"carrier_code": "SJ",
"departure_airport": { "iata": "LTN" },
"arrival_airport": { "iata": "OTP" },
"scheduled_departure_at": "2026-08-02T06:15:00.000Z",
"scheduled_arrival_at": "2026-08-02T09:40:00.000Z"
},
"boarding": { "gate": "C14", "seat": "14C" },
"booking": { "pnr": "R7K92M" }
}
}Path parameter:
| Parameter | Description |
|---|---|
id | The pass to update. Accepts the Revolut pass ID (UUID) or your partner_reference prefixed with ref:. |
Header parameters:
| Parameter | Description |
|---|---|
Revolut-Api-Version | API version of the request, e.g. 2026-07-01. |
Idempotency-Key | UUID for safe retries. See Idempotency below. |
You can also address the pass by ref: + your partner_reference (e.g. PUT /passes/ref:skyjet-2241-R7K92M). Use this form when you don't have the Revolut-assigned id - for example, auto-provisioned passes where Revolut never returned an id.
Body parameters:
| Parameter | Description | Required |
|---|---|---|
partner_reference | Your own key for the pass - used to match it in your system | Yes |
type | Pass type discriminator - boarding_pass, event_pass, or loyalty_pass | Yes |
version | Monotonically increasing version - bump on every change so Revolut can detect stale updates | Yes |
semantics | Full pass content for the type. This is a full replacement - include everything | Yes |
relevant_at | When the pass becomes time-relevant (departure time, event start) - drives Revolut's polling cadence | No |
actions | Buttons rendered on the pass (e.g. "Open website", "Get directions") | No |
triggers | Context-aware notifications (e.g. proximity alert, time before event) that nudge the customer | No |
template_id is not accepted on updates - it's immutable and set at creation.
Set relevant_at to the event time (departure, event start) so Revolut polls more frequently around the event and less aggressively afterwards.
Idempotency
Send an Idempotency-Key header (UUID) with every push update. Retrying with the same key and the same body is safe - Revolut returns the original 204 without applying the update twice. Reusing the same key with a different body returns 409.
Revolut uses optimistic concurrency: if the version you send doesn't match the stored version +1, the update is rejected with 409. This prevents a slower update from overwriting a newer one.
If you get 409, re-fetch the pass, apply your changes to the current state, bump version, and retry.
If you also want Revolut to pull updates on its schedule - for example, after an app relaunch or sync cycle - implement the poll endpoint.
2. Serve poll requests from your backend
To serve poll requests, you need to host an HTTPS endpoint on your server that accepts GET /passes/{partner_reference} requests from Revolut. Register your callback base URL with the Revolut Wallet team during onboarding. Revolut will call it on a schedule and on demand to fetch the latest state of a pass.
Your endpoint must:
-
Accept
GETrequests at/passes/{partner_reference} -
Return the full pass body (without
template_id) when the pass has changed -
Respond with
304 Not Modifiedwhen the pass hasn't changed since the suppliedETag -
Validate Revolut's authentication (see Wallet Partner Callbacks authentication)
-
Support the response codes listed
-
![Request]
Revolut requestGET /passes/{partner_reference} Host: https://passes.partner.example/revolut-wallet Revolut-Api-Version: 2026-07-01 Request-Id: 01970c4c-5790-7fb5-8331-84dfa075bbe9 If-None-Match: "v3"Path parameters:
Parameter Description partner_referenceThe partner reference of the pass Revolut tries to poll. Header parameters:
Parameter Description Revolut-Api-VersionAPI version of the request. Request-IdUnique identifier Revolut assigns to this poll attempt. Use it for tracing and idempotency on your side. If-None-MatchThe ETagRevolut received from your last200response. If the pass hasn't changed since, respond304 Not Modified. -
![Response]
Example response - updatedHTTP/1.1 200 OK Content-Type: application/json ETag: "v4" { "partner_reference": "skyjet-2241-R7K92M", "type": "boarding_pass", "version": 4, "semantics": { "passenger": { "full_name": "Sarah Connor" }, "flight": { "flight_number": "2241", "carrier_code": "SJ", "departure_airport": { "iata": "LTN" }, "arrival_airport": { "iata": "OTP" }, "scheduled_departure_at": "2026-08-02T06:15:00.000Z", "scheduled_arrival_at": "2026-08-02T09:40:00.000Z" }, "boarding": { "gate": "C14", "seat": "14C" }, "booking": { "pnr": "R7K92M" } } }Header parameters:
Parameter Description ETag(header)Entity tag for this version of the pass. Return a fresh value with every 200- Revolut sends it back asIf-None-Matchon the next poll, which lets you respond304when nothing has changed.Body parameters:
Parameter Description partner_referenceYour reference for the pass. typePass type discriminator. versionCurrent pass version. semanticsFull pass content for the type. Handle unchanged passes
Revolut sends
If-None-Matchwith theETagfrom your previous200response. If the pass is unchanged, respond304 Not Modified(no body) to save bandwidth and processing.Example response - unchanged304 Not Modified ETag: "v3"
Response codes and actions
| Code | Description | Action |
|---|---|---|
200 | Pass returned with a fresh ETag. Revolut updates the pass in the customer's wallet. | Include the full pass body and a fresh ETag header. |
304 | Pass unchanged since the supplied ETag. Revolut keeps the existing pass. | Return no body. No processing needed on your side. |
404 | Pass not found. Revolut stops polling this pass. | Return this when the pass was never created or has been deleted. Revolut won't retry. |
410 | Pass permanently gone. Revolut stops polling this pass. | Return this when the pass was voided or refunded (e.g. cancelled booking). Use instead of 404 when the pass existed but is no longer valid. |
429 / 503 | Temporary capacity issue. Revolut retries after Retry-After. | Include a Retry-After header with the seconds to wait. Revolut will retry after the delay. |
Implementation checklist
Work through these checks in order - implement, test in sandbox, then validate on production before going live.
- Complete onboarding and create at least one pass
- Push: implement
PUT /passes/{id}calls withversionbumping - Push: decide whether to address by Revolut
idorref:partner_reference - Poll: implement a
GET /passes/{partner_reference}endpoint on your server - Poll: support
ETag/If-None-Match/304 Not Modified - Poll: return correct status codes (
200,304,404,410,429/503) - Test optimistic concurrency by sending a stale
version(expect409)
Sandbox testing:
- Push an update with a bumped
versionand confirm the pass changes in the sandbox wallet - Push the same body with the same
versionand confirm it's a no-op - Push a different body with a stale
versionand confirm you get409 - Have Revolut poll your endpoint and confirm you return
200with a freshETagwhen the pass changed - Have Revolut poll with
If-None-Matchmatching yourETagand confirm you return304 - Verify
ref:addressing works for a pass where you don't have the Revolutid
Production validation before go-live:
- Push a live update and confirm it reaches real Revolut Wallets
- Confirm Revolut can poll your production endpoint and receives the correct pass state
- Validate that
ETag/304flow works end-to-end in production