Issue Apple & Google Wallet passes from your backend
Last verified: August 2026
What it takes to put a pass in a customer's wallet programmatically — certificates, issuance, updates, push, idempotency — and how to ship it in an afternoon with a REST API instead of a platform build.
The build-vs-buy reality
Apple Wallet passes are signed .pkpass bundles; Google Wallet objects are JWT-backed API resources. Building direct means: an Apple Developer account ($99/yr), a Pass Type ID certificate you rotate before expiry, a signing pipeline, a Google Wallet issuer account, push-update plumbing for both rails (APNs for Apple, object updates for Google), and a device-registration web service. None of it is hard; all of it is undifferentiated maintenance.
A wallet-pass API collapses that to HTTPS calls: you POST an issue request, the platform signs, hosts, and pushes. The rest of this guide shows the full lifecycle in curl, then a checklist of what to demand from any provider.
The lifecycle in three calls
1. Issue a pass — every identifier is optional: email, phone, passstudioHolderId (Pass Studio's holder id, from a holder lookup), your own customerId, or none at all for an anonymous card. Whatever identifier you send is the dedupe key, scoped per pass: re-issuing the same pass to the same identifier returns the existing card (alreadyExisted: true), never a duplicate — safe to wire to any order or signup event — while the same customer holds as many different passes as you issue them. When one customer should hold several cards of the same pass (three seats to one show), send a distinct barcodeValue per card — dedupe then keys on the code, one card per ticket. That's also the field for codes your own scanners already accept (a CRM id, a membership number); without it, Pass Studio mints the code per the pass's barcode settings.
Delivery is explicit, not magic. When the call carries an email, Pass Studio by default sends the customer a branded email with the Apple / Google Wallet buttons — pass sendEmail: false to deliver the response's addToWalletUrl yourself. Any other identifier emails nobody — the link is yours to send. The pass is never force-installed: the customer taps the link and adds it to their wallet themselves.
An SMS-only customer list is a first-class case: issue by phone and text the addToWalletUrl through your own SMS rail. The phone becomes real holder identity — matched right-to-left on digits, so formatting and country-code variants resolve to the same customer. Later calls address the card by instanceId or barcodeContent, or the customer by email or phone.
curl -X POST https://www.passstudio.online/api/v1/passes/PASS_ID/issue \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/json" \
-d '{
"email": "alex@example.com",
"fields": { "memberName": "Alex Rivera" },
"utmSource": "facebook", "utmMedium": "cpc", "utmCampaign": "spring24"
}'The three utm* fields are optional first-touch attribution, stored verbatim: every event this card ever emits — install, scan, redemption — carries the campaign into your analytics.
2. Update the pass in their wallet — fields, barcode, and an optional lock-screen message in one PATCH; the pass refreshes on the phone with a push.
curl -X PATCH https://www.passstudio.online/api/v1/instances/fields \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/json" \
-d '{ "instanceId": "…", "fields": { "tier": "Gold" }, "message": "You are Gold now, Alex." }'3. Revoke when the membership ends — the card goes grey in Apple Wallet and is removed from Google Wallet.
curl -X POST https://www.passstudio.online/api/v1/instances/revoke \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/json" \
-d '{ "instanceId": "…", "reason": "membership ended" }'Full surface: 11 endpoints — issue, template fields, per-holder fields, holder lookup, points, order processing, three revoke shapes, redeem, and /me. Full API reference →
Idempotency: the part most wallet APIs get wrong
Automations retry. Webhooks double-fire. Zapier replays. If your wallet API isn't idempotent, retries mean double-issued passes, double-awarded points, and holders getting the same push banner twice.
Every mutating Pass Studio endpoint takes an externalRef idempotency key. A replayed ref returns the stored original result with alreadyProcessed: true — no second push, no second notification, no double award. Issue calls are idempotent per email; revoke is naturally idempotent. Compose refs as <source>_<externalOrderId> and a POS receipt, a Shopify webhook, and a manual retry all collide into one operation.
Billing is part of the contract, not a surprise: a change-free update returns pushSkipReason: "no_change" and no charge; at zero balance an update that can't push returns 202 (data applied, push skipped) and a blocked call returns 402 before the ref is claimed — the status code tells your client exactly whether the intent executed.
What to demand from any wallet-pass API
Use this list on any provider, ours included:
- No forced Apple Developer account. You should be live without buying Apple's $99/yr membership or handling .p12 certificates. Bring-your-own-cert should be optional, for white-label issuer naming.
- Both rails, one call. Apple and Google from the same issue request — not two integrations.
- Idempotency keys on every mutation. See above. Ask the vendor what a replayed request returns.
- Push updates included — with an explicit contract for skipped or failed pushes.
- Webhooks out, so your system hears when passes are issued, updated, redeemed, or removed. (Ours:
pass.issued,pass.updated,pass.removed,pass.redeemed,holder.merged.) - Action-based pricing. Per-active-pass billing means your bill grows while passes sit idle in wallets. Pay for issues/updates/scans, not existence.
- A published rate-limit contract. (Ours: 600 requests/minute per key,
429+rate_limitedover it — details.) - Documented error codes and a status contract you can build retry logic against.
- An escape hatch below code: forms, share links, or Zapier for the flows that don't deserve engineering time.
- A data path to your warehouse — event streaming (e.g. a native Segment source) rather than CSV exports, with attribution your stack already speaks: pass
utmSource/utmMedium/utmCampaignat issuance and every event the card ever emits carries the campaign, first-touch, all the way to redemption.
Where the no-code line sits
Not everything deserves an integration. Pass Studio's ladder: share links and QR codes (zero code) → embedded forms → Zapier → REST API. Start at the bottom, write code only where volume or logic demands it. The API and the no-code rails hit the same billing, identity, and analytics substrate, so you can mix per use case.
FAQ
Do I need an Apple Developer account to issue Apple Wallet passes?
Not with Pass Studio — passes sign under our certificate by default. Direct integration with Apple requires one ($99/yr) plus a Pass Type ID certificate you maintain.
How do wallet passes update after they're issued?
The platform pushes changes: Apple passes refresh via APNs and re-fetch, Google objects update server-side. Through the API this is one PATCH call, optionally with a lock-screen message.
Can I try it before paying?
Every account starts with 50 free credits — enough to issue, update, and revoke real passes on both rails before paying anything.
What does it cost?
Pay-as-you-go credits consumed by actions (issue, push, scan). No monthly platform fee, no per-active-pass charge.
Can I trigger passes from Shopify or Square without the API?
Yes — native integrations auto-issue from Shopify paid orders and run a Square register loop. The API is for everything else.
Comparing providers? Best wallet pass platforms in 2026 → · Coming from PassKit? PassKit vs Pass Studio → · Pricing details: Credits & billing →
Ready to make the first call? Create an account — 50 free credits →