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 gray 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. Delivery included. An issue call that carries an email should send the branded wallet email itself — both wallet buttons, the customer's language, your sender name — so you don't stand up a transactional-email pipeline just to deliver a pass. (Ours: on by default, reported as emailSent; sendEmail: false hands you the link instead.)
  4. Idempotency keys on every mutation. See above. Ask the vendor what a replayed request returns.
  5. Push updates included — with an explicit contract for skipped or failed pushes.
  6. 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.)
  7. Action-based pricing. Per-active-pass billing means your bill grows while passes sit idle in wallets. Pay for issues/updates/scans, not existence.
  8. A published rate-limit contract. (Ours: 600 requests/minute per key, 429 + rate_limited over it — details.)
  9. Documented error codes and a status contract you can build retry logic against.
  10. An escape hatch below code: forms, share links, or Zapier for the flows that don't deserve engineering time.
  11. 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. Bulk onboarding has a no-code rail too: the Studio's CSV import takes memberId, name, email, phone rows, up to 500 per batch, previews the credit cost, emails the wallet link to every row with an address, and reports rows whose email already holds an active card instead of minting them twice.

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.

Does the API deliver the pass to the customer, or do I have to email it myself?

Both. An issue call with an email sends a branded wallet email with the Apple and Google Wallet buttons, in the customer's language, from your sender name — the response reports emailSent. Pass sendEmail: false to receive the link and deliver it through your own channel.

Can I load an existing member list without writing code?

Yes — the Studio's CSV import takes memberId, name, email and phone rows (up to 500 per batch), previews the credit cost, emails the wallet link to every row with an address, and skips rows whose email already holds an active card. The API issue endpoint does the same thing one call at a time.

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 →