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:

  1. 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.
  2. Both rails, one call. Apple and Google from the same issue request — not two integrations.
  3. Idempotency keys on every mutation. See above. Ask the vendor what a replayed request returns.
  4. Push updates included — with an explicit contract for skipped or failed pushes.
  5. 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.)
  6. Action-based pricing. Per-active-pass billing means your bill grows while passes sit idle in wallets. Pay for issues/updates/scans, not existence.
  7. A published rate-limit contract. (Ours: 600 requests/minute per key, 429 + rate_limited over it — details.)
  8. Documented error codes and a status contract you can build retry logic against.
  9. An escape hatch below code: forms, share links, or Zapier for the flows that don't deserve engineering time.
  10. 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 / utmCampaign at 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 →