Wallet passes
Orbit can issue digital passes for Apple Wallet and Google Wallet: loyalty cards, coupons, and event tickets your customers save to the wallet app on their phone. Issue them through the API or from the dashboard’s issuance builder, and deliver them by putting the pass’s save link in any message you send — SMS, WhatsApp, or email. For the lifecycle semantics behind these steps — the state machine, the generation counter, and the ledger — see the wallet pass lifecycle concept. Three pass types are supported today. Pick the type first, because it decides how the holder redeems it:- Loyalty cards — points balance, tier, or membership number behind a scannable barcode. Reach for this when the pass grants standing status: reward membership, tier, or a member number the staff scans at checkout.
- Coupons — an offer with a redemption barcode and an optional expiry date. Reach for this when the pass is the discount a holder redeems once — a percentage off, a free item, a gift-with-purchase. Set
expires_atso the offer dates itself. - Event tickets — a one-time or dated piece of access a holder gets admitted with: seat, gate, order number. Reach for this when the staff needs to scan to admit, not to look up a balance.
The pass lifecycle
Every pass moves through the same lifecycle:- Issue —
POST /wallet-passes/issuecreates the pass and returns both platform payloads (an Applepass.jsonstructure and a Google “Save to Wallet” link). New passes startactive. - Update —
POST /wallet-passes/:id/updatepatches an active pass’s content (points balance, tier, coupon expiry, ticket seat). Each accepted update bumps the pass’sgenerationcounter, which is the signal that the holder should refresh the pass on their phone. - Void —
POST /wallet-passes/:id/voidpermanently revokes an active pass. Voiding is terminal: a voided pass reportsstatus: "voided"and rejects any further update or re-void with a409 CONFLICTerror.
Issuing a pass
The same issue call in four languages:Apple vs Google templates
One issue call produces both platform payloads. The same inputs resolve differently per platform, so know how each renders them:- Apple Wallet (
pass.json) —titlemaps to the primary field; the first four entries offieldsbecome secondary fields rendered under it. Apple types pick their visual template by class — a coupon scans like a coupon, a ticket like a ticket — so send the realtypeeven where the fields overlap. - Google Wallet (
genericobject) —titlebecomes the card header,subtitlethe subheader, and eachfieldsentry becomes a text module row rendered as label + value, so all fields show without the four-item cap Apple applies.
type by the holder’s redemption behavior, not which platform renders it — both platforms agree on the QR_CODE / PDF_417 / AZTEC / CODE_128 barcode formats, so the encoded value is portable.
Response
idempotency_key (or an Idempotency-Key header) and a retry returns the original pass with replayed: true instead of minting a second one. The replayed response reflects the pass’s current state — so a delayed retry after an update or void never reports stale content.
Platform status
Before issuing, check which platforms are live:apple—configured: truewhen the Apple Wallet pass-type id and team id are connected. Until thenpass_jsonisnull, because there is no valid certificate identity to stamp the pass with.google—configured: truewhen the Google Wallet issuer id and service-account credentials are connected. Until thensave_urlisnull.types/barcode_formats— the enums the issuance form can offer; use these to drive your own UI validation.
Signed issuance links
Each platform has its own signing mechanism:- Apple Wallet builds the pass as a signed
.pkpassbundle: a PKCS#7 signature over a manifest of the pass files, using the Apple-issued pass signing certificate tied to your pass type. Orbit generates the unsignedpass.jsoncontent (platforms.apple.pass_json); the final binary signing step runs on the transport that holds the certificate material, once the Apple credentials are connected. - Google Wallet builds a signed “Save to Google Wallet” link (
platforms.google.save_url): the pass is wrapped in a Google generic object and encoded as an RS256-signed token appended tohttps://pay.google.com/gp/v/save/.... Opening that link on an Android device or in a browser adds the pass to the holder’s Google Wallet account.
Listing, updating, and voiding passes
List issued passes (newest first) with optional filters:generation. To clear a field, send it as explicit null.
Void a pass (optionally with a reason):
status: "voided", renders as expired in the wallet, and cannot be updated.
Example: issue a pass when a commerce checkout completes
The pattern is one pass per completed order, driven off the customer-data event stream: when your CDP ingests aorder_completed event from the commerce checkout spine, you trigger the wallet-pass issue call — the spine tells you that the order completed; the pass API records a coupon pass got issued for it.
-
Ingest the event. When commerce finishes collecting payment, fire the documented event into the CDP following your commerce integration — the
order_completedverb carries the order total, currency, channels, and reference. See the attribution-conversion events reference for the event ingestion patterns. -
Issue the pass with the order as your correlation key. Point an integration worker — or a flow — at the event and call issue with
external_idset to the order reference, so the pass traces back to the purchase that earned it:Theidempotency_keyderived from the order reference matters here: event delivery can retry, and the keyed issue call returns the original pass instead of stacking duplicate coupons. -
Send the save link on the channel the buyer used. The commerce spine surfaces still hand you back a contact bound to a channel — SMS, WhatsApp, email. Take
data.platforms.google.save_url(or the Apple payload once Apple credentials are connected) and send it through the standard messaging endpoint the same way as the enrollment flow below. Recovery gifting works the same way: when a flagged-abandoned cart recovers and completes, a follow-up coupon pass is a second-touch offer, not a duplicate.
Example: enroll a loyalty pass from an SMS deep link
The most common onboarding flow: a customer joins your loyalty program, and you text them their wallet pass.-
Issue the pass tied to the enrolling contact:
-
Pick the save link from the response —
data.platforms.google.save_urlfor Google Wallet holders (the Apple Wallet equivalent is the signed pass the.pkpasstransport serves once Apple credentials are connected). -
Send the link over SMS using the standard messaging endpoint:
Keep the message brand-clean — save links are long, so route them through Orbit link shortening if you want a short URL. The same deep-link pattern works on SMS, WhatsApp, or email; pick whichever channel the customer opted into.
The dashboard issuance builder
Operators can run the whole lifecycle without the API. In the dashboard, open Marketing → Wallet passes (also reachable at Outbound → Wallet passes). The surface is gated to owner, admin, and developer roles.- Issue a pass — the New pass form offers exactly the pass types and barcode formats
GET /wallet-passes/platformsreports, and flags when a platform is not yet connected. - Update content — edit points, tiers, or other fields on an active pass; each accepted edit bumps the pass generation.
- Void a pass — revoke through a confirmation dialog; voided passes remain visible in the list for audit.
- Copy save links — copy the Google save link (or read the Apple payload status) to drop into a message.
How issuance state is stored
Wallet-pass state lives as events in the same append-only customer-data ledger that powers loyalty programs and incentive issuance. Issuing appends an issuance event; updates and voids append overlay events; and the current state of a pass is folded from its event stream on every read. There is no separate wallet-passes table to migrate, and nothing external to keep in sync — which is also what makes idempotent replays safe: the replayed response folds to the pass’s current lifecycle state. All wallet-pass endpoints are scoped withcontacts:read (list/get/platforms) and contacts:write (issue/update/void) — a pass is customer-facing collateral tied to a contact.
Metrics and the ledger events
Every lifecycle step writes a customer-data-platform event, so the pass program is measurable end-to-end without any extra reporting plumbing:wallet_pass.issued— one event per issued pass (the pass-creation counter).wallet_pass.updated— one event per accepted content edit (the generation-bump counter).wallet_pass.voided— one event per terminal revocation.
GET /cdp/events/debug?event_name=wallet_pass.issued — or export to your warehouse destination as the events roll in. Aggregate them by tenant, contact, or pass type to answer install/redemption-style funnels: issued minus voided is the active pass count; updated divided by issued is how often customers are being re-engaged with refreshed content. Because issuance rides the same ledger a loyalty program or an incentive writes to, a order_completed-driven coupon ties into the same funnel too.