Passmint
TemplatesDocsPricing
Log inGet started
Passmint
TemplatesDocsPricingTermsPrivacy

© 2026 Passmint. Built for indie makers.

Getting started

  • Overview
  • Quickstart

API

  • REST API reference
    • Authentication
    • Passes
    • Templates
    • Events
    • Webhooks
    • Errors
  • Webhooks
    • Event types
    • Creating a webhook
    • Payload format
    • Verifying signatures
    • Retries
  • Node.js SDK
    • Configuration
    • Passes
    • Templates
    • Webhook signatures
    • Errors
    • Idempotency

Open source

  • Passmint Package
Open source

Generate wallet passes from any runtime.

passmint is an open-source TypeScript library for creating Apple Wallet and Google Wallet passes. One unified schema, two outputs — a signed .pkpass for iOS and a Google Wallet save-link JWT for Android. Web Crypto only, so it runs on Cloudflare Workers, Vercel Edge, Deno, Bun, and Node 20+ without polyfills.

GitHubnpm
01

When to use passmint

The passmint package is ideal when you want full control over pass generation — you manage your own Apple certificates, Google service accounts, and signing infrastructure. It gives you the lowest-level building block: take a pass definition in, get signed bytes or a save-link out.

This is a good fit if you:

  • Need to run on edge runtimes or serverless workers
  • Want to self-host the entire pass pipeline
  • Are building a platform that issues passes on behalf of others
  • Prefer open-source dependencies you can audit and fork
02

When to use the Passmint platform

Generating a pass is only the first step. In production, you also need to distribute passes to holders, track installs and engagement, handle certificate renewals, push live updates to installed passes, and process webhook events — all while managing templates across your team.

The Passmint platform handles all of this so you can focus on your product:

  • Visual designer — design passes in a live phone-frame preview, no code required
  • Managed certificates — no .p12 files, no WWDR chains, no renewal calendar
  • Distribution tracking — views, downloads, and installs from day one
  • Live updates — push field changes to passes already in a holder's wallet
  • Webhooks — get notified when passes are installed, updated, or removed
  • REST API + Node SDK — typed client with retries, idempotency, and webhook verification

Many teams start with passmint for prototyping and move to the platform when they need the full lifecycle. Get started with the platform →

03

Install

npm install passmint

Requires Node 20+ or any runtime with Web Crypto. ~21 KB gzipped.

04

Apple Wallet

Load your Apple Pass Type ID certificate, WWDR intermediate, and private key once, then sign passes. Keys must be in PKCS#8 PEM format — convert with openssl pkcs8 -topk8 -nocrypt -in key.pem -out key.pkcs8.pem.

import { Pass, SigningMaterial } from "passmint"

const material = await SigningMaterial.fromPem({
  signerCertPem: process.env.APPLE_PASS_CERT!,
  wwdrPem: process.env.APPLE_WWDR_CERT!,
  privateKeyPkcs8Pem: process.env.APPLE_PASS_KEY!,
})

const signed = await Pass.eventTicket({
  passTypeIdentifier: "pass.com.example.event",
  serialNumber: crypto.randomUUID(),
  teamIdentifier: "ABCD1234EF",
  organizationName: "Example",
  description: "Concert ticket",
  images: { icon: { x2: { bytes: iconPng } } },
  barcodes: [{ format: "qr", message: "TICKET-xyz" }],
})
  .primaryField({ key: "event", label: "Event", value: "Beyoncé Live" })
  .secondaryField({ key: "loc", label: "Location", value: "Apple Park" })
  .sign(material)

On Cloudflare Workers, build SigningMaterial once per isolate and reuse it across requests for best performance.

05

Google Wallet

Use a Google Cloud service account to generate a save-link JWT. Drop the resulting URL into an <a> tag with Google's "Add to Google Wallet" button.

import { Pass, GoogleSigningMaterial } from "passmint"

const google = await GoogleSigningMaterial.fromServiceAccount({
  clientEmail: serviceAccount.client_email,
  privateKeyPkcs8Pem: serviceAccount.private_key,
  issuerId: "3388000000000000",
})

const pass = Pass.eventTicket({
  passTypeIdentifier: "pass.com.example.event",
  serialNumber: "ticket-42",
  teamIdentifier: "ABCD1234EF",
  organizationName: "Example",
  description: "Concert ticket",
  images: { icon: { x2: { bytes: iconPng } } },
  barcodes: [{ format: "qr", message: "TICKET-42" }],
}).build()

const url = await pass.toGoogleSaveLink(google, {
  origins: ["example.com"],
})
// → "https://pay.google.com/gp/v/save/<jwt>"
06

One schema, two wallets

The same Pass produces both outputs — define your pass once:

const pass = Pass.eventTicket({ /* ... */ }).build()

const pkpass = await pass.sign(appleMaterial)
const saveLink = await pass.toGoogleSaveLink(googleMaterial, {
  origins: ["example.com"],
})
07

Pass styles

Five styles are supported, each mapping to the corresponding Apple and Google pass types:

  • Pass.eventTicket() — event tickets
  • Pass.boardingPass() — air, train, bus, boat
  • Pass.storeCard() — loyalty cards
  • Pass.coupon() — coupons and offers
  • Pass.generic() — generic passes

Per-style field-count limits are enforced at construction time. Use the fluent builder (Pass.eventTicket(...).primaryField(...)) or the raw object API (Pass.from(...)).

08

Output formats

signed.toUint8Array()     // Promise<Uint8Array>
signed.toStream()         // ReadableStream<Uint8Array>
signed.toResponse(init?)  // HTTP Response with content-type set

toResponse() sets Content-Type: application/vnd.apple.pkpass and a Content-Disposition: attachment header so the pass installs on tap.

09

Platform-specific escape hatch

For fields the unified schema doesn't model — smart-tap redemption, rotating barcodes, Apple semantic tags — use applyRaw:

Pass.eventTicket({
  // ...
  applyRaw: {
    apple:  { sharingProhibited: true },
    google: { smartTapRedemptionValue: "nfc-payload" },
  },
})

applyRaw.apple deep-merges into pass.json. applyRaw.google deep-merges into the generated Google object.

10

Supported runtimes

Works anywhere with Web Crypto, Web Streams, and Uint8Array. No node:* imports, no Buffer.

  • Node.js 20+
  • Cloudflare Workers
  • Vercel Edge Functions
  • Deno
  • Bun
  • Supabase Edge Functions
  • Netlify Edge Functions
11

Errors

Every throw extends PassmintError with a stable code property:

  • PassmintSchemaError — input validation (includes full Valibot issue list)
  • PassmintRenderError — Apple / Google render layer
  • PassmintSigningError — CMS signing, key import, unsupported key format
  • PassmintPackagingError — ZIP assembly
  • PassmintGoogleError — Google save-link JWT