Wallet pass lifecycle
A wallet pass in Orbit is a digital card — a loyalty card, coupon, or event ticket — rendered for Apple Wallet and Google Wallet from one platform-agnostic content model. This page explains the lifecycle semantics: the three pass types and when to choose each, the issue → update → void state machine, thegeneration counter that tells holders to refresh, the append-only ledger the state is projected from, and how delivery works. For the step-by-step issuance workflow and full field reference, see the Wallet Passes channel guide; for the architecture this lifecycle shares with incentives and loyalty, see the event-ledger projection model.
The three pass types
Every issue call picks one of three pass types — the type is fixed for the pass’s lifetime and chooses the layout each wallet renders:
The type drives one thing — the visual layout. Barcodes, colors, fields, expiry, and the lifecycle all behave identically across the three. Check
GET /wallet-passes/platforms for the supported types list before building your own issuance UI.
The lifecycle: issue → update → void
A pass moves through exactly one path:- Issue.
POST /wallet-passes/issuestores the full content snapshot and returns both platform payloads — an Applepass.jsonstructure and a Google “Save to Wallet” link — withstatus: "active"andgeneration: 0. A fresh issue returns201; a replayed issue (same idempotency key) returns200. - Update.
POST /wallet-passes/:id/updatepatches an active pass’s content — a points balance, a new tier, a coupon expiry, a seat change. Only the fields you send change; everything else carries forward, and sending a field as explicitnullclears it. Each accepted update bumpsgenerationby one. - Void.
POST /wallet-passes/:id/voidpermanently revokes the pass, optionally recording areason. Void is terminal: the pass reportsstatus: "voided", renders as expired in the holder’s wallet, and any further update or re-void returns409 CONFLICTnaming the current status. A voided replacement is always a new pass — issue again.
update and void run under a per-pass advisory lock, so two concurrent transitions on the same pass apply in a well-defined order; the loser gets the 409 against the post-transition state.
The generation counter
generation is the pass-changed signal, not a version to diff. It starts at 0 at issue and increments by exactly one on every accepted update. Compare it across polls to learn “something moved” without diffing content fields, and treat each bump as the trigger a wallet push-registration step would key off to tell Apple or Google Wallet to refresh the holder’s copy. Because reads fold the full event history at request time, the counter — like everything else on a read — is current, never cached.
The append-only ledger
Pass state lives in the tenant’s append-only event store — the same store the CDP, loyalty balances, and incentive issuance use. Issue appends awallet_pass.issued event carrying the full content snapshot; each update appends a wallet_pass.updated event carrying only the changed fields; void appends a terminal wallet_pass.voided event. The pass you read is a projection folded from that stream on every request — no separate table, no migration, no background job that can fall behind.
What that buys you:
- Audit. The issued event keeps exactly what the holder saved, forever. “What did this coupon say when the customer installed it?” is answered by the original event, not reconstructed from edits.
- No drift. A GET returns the fold computed at request time; there is no sync layer between storage and response.
- Safe transitions. Update and void re-read, validate, and append inside one transaction under the per-pass lock.
The idempotency key
Issuance is idempotent. Send anidempotency_key in the body (or an Idempotency-Key header), and a retry returns the first issued pass with replayed: true instead of minting a duplicate — the key lookup and the append run inside one transaction, so a double-submit or a client retry can never create two passes for one enrollment.
The replay is folded to the pass’s current lifecycle state: if the original pass was updated or voided between the first attempt and the retry, the replayed response reflects that — it never reports stale issued-time content.
Delivery: the save link is just a link
Issuing returns the delivery artifacts in the response; there is no separate “send” step inside the wallet-passes API.- Google Wallet produces a
platforms.google.save_url— an RS256-signed “Save to Google Wallet” link you drop into an SMS, WhatsApp, or email message. Opening it adds the pass to the holder’s wallet. - Apple Wallet produces
platforms.apple.pass_json— the unsigned pass structure. The binary signature step (a PKCS#7 signature over the.pkpassbundle) runs on the transport that holds the Apple signing certificate, connected via your Apple developer account.
save_url → send it as the message body.” A worked example lives in the channel guide.
Where passes end: boundaries with loyalty and incentives
The pass is the carrier, not the program or the payout:- Loyalty. Points balances, tiers, earn and burn rules live in the loyalty program model. The loyalty card pass is the surface a member installs; the program is what earns and burns the points the card displays. Rendering the wallet-pass label is one of several tier renderers.
- Incentives. Redemption — minting a discount code, calling a gift-card network, recording a manual payout — routes through the incentives fulfillment engine. The pass shows the reward; incentives pay it out.
- Lifecycle. The issue/update/void machine documented here applies to passes only. Loyalty config resets and incentive voids are separate surfaces following the same ledger pattern (see the projection model).
Example: the lifecycle in three calls
Idempotent issue (a retry with the same key returns200 + replayed: true, not a second pass):
null clears a field; generation increments:
409 CONFLICT:
409 CONFLICT naming the pass’s current status.
Platforms and barcode formats
Two platforms, four barcode symbologies:GET /wallet-passes/platforms reports each platform’s configured flag (with a machine-readable reason when not connected), the supported types, and the barcode_formats. You can issue and manage passes with no platform connected — the content is stored and readable — and the downloadable payloads appear the moment credentials are connected, because every read rebuilds them fresh.
Related reading
- Wallet Passes channel guide — the step-by-step issuance workflow and field reference.
- The event-ledger projection model — the shared ledger architecture passes, incentives, and loyalty project over.
- Loyalty program model — the points-and-tiers program a loyalty-card pass carries.
- Incentives: catalog, ledger, and fulfillment — the engine pass redemptions route through.
- Idempotency and safe retries — the retry discipline the idempotency key extends.