Passmint
TemplatesIntegrationsDocsPricing
Log inGet started
Passmint

Apple and Google Wallet passes from one API. Built for people who ship.

  • GitHub
  • X
Product
  • Pass Designer
  • Developers
  • Distribution
  • Analytics
  • Templates
  • Integrations
  • Pricing
Resources
  • Free tools
  • Wallet card generator
  • pkpass validator
  • Pass anatomy
  • Live demo
  • Blog
  • Glossary
  • Compare
Developers
  • Documentation
  • API reference
  • Node SDK
  • Webhooks
  • MCP server
Company
  • Changelog
  • About
  • Contact
  • Support
  • Security
  • Terms
  • Privacy

© 2026 Passmint.

Apple Wallet & Google Wallet, one API.

Getting started

  • Overview
  • Quickstart

API

  • REST API reference
    • Authentication
    • Passes
    • Templates
    • Events
    • Webhooks
    • Errors
  • Pass images
    • Image slots
    • How images resolve
    • Stamp card example
    • Member photo example
    • Google Wallet
    • Privacy
  • Webhooks
    • Event types
    • Creating a webhook
    • Payload format
    • Verifying signatures
    • Retries
  • MCP server
    • Install
    • The endpoint
    • Other clients
    • Read-only mode
    • Tools
    • Things worth knowing
    • Example prompts
    • What it doesn't do
    • Rate limits
  • Node.js SDK
    • Configuration
    • Passes
    • Templates
    • Webhook signatures
    • Errors
    • Idempotency

Open source

  • Passmint Package
Real-time events

Webhooks.

Get notified in real time when passes are installed, updated, or removed from a holder's wallet. Webhooks let you react to lifecycle events without polling. Just point us at a URL and we'll deliver signed payloads as they happen.

01

Event types

Every webhook delivery includes a type field identifying the event. Subscribe to the ones you care about, or use "*" to receive all of the pass events.

template.republished is the exception: it is sent only to endpoints subscribed to it by name, and "*" does not include it. Its payload has data.object.template_republish and no pass, and GET /v1/events does not list it. The dashboard leaves it unticked by default when you add an endpoint.

  • pass.issued: A new pass was signed and issued.
  • pass.add_intent: A holder took a step toward adding the pass, either downloading the file or tapping Add to Google Wallet.
  • pass.added_to_wallet: The pass was added to a holder's Apple Wallet or Google Wallet.
  • pass.update_pushed: A pass's field values changed and the update reached at least one installed wallet. Not sent when no wallet holds the pass or every push failed.
  • pass.update_delivered: A device pulled the updated pass (Apple-confirmed delivery).
  • pass.update_failed: An update or void was sent and a wallet platform refused it: Apple rejected a push, or the Google Wallet object could not be patched. data.object.delivery_failure says which platform and why. Not sent when no wallet holds the pass.
  • pass.removed: The pass was removed from a holder's wallet.
  • pass.voided: The pass was voided.
  • pass.redeemed: The pass was scanned and accepted at a scanner device. Rejected scans do not fire this event.
  • template.republished: A template republish finished: every pass it reached has the new design, and data.object.template_republish has the counts. Sent only to endpoints subscribed to this event by name, not to *.
02

Creating a webhook

Register an endpoint with POST /v1/webhooks. Provide a url (required) and an events array (required). You can optionally include a description and an enabled flag (defaults to true). Use "*" in the events array to subscribe to all pass event types (not template.republished).

curl https://api.passmint.com/v1/webhooks \
  -H "Authorization: Bearer pmk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/passmint",
    "events": ["pass.added_to_wallet", "pass.removed"],
    "description": "Track wallet installs"
  }'

The signing secret (whsec_xxx) is returned only on creation: store it immediately. You will not be able to retrieve it again.

03

Payload format

Every delivery sends a JSON body with the event metadata and the affected pass:

{
  "id": "psEvnt_abc123",
  "object": "event",
  "type": "pass.added_to_wallet",
  "api_version": "2026-06-26",
  "created_at": "2026-04-16T12:00:00.000Z",
  "idempotency_key": "aapl:registered:pass_xxx:dlh_3f2a...:v1",
  "livemode": true,
  "data": {
    "object": {
      "pass": {
        "id": "pass_xxx",
        "short_id": "abc123",
        "template_id": "tmpl_xxx",
        "organization_id": "org_xxx",
        "serial_number": "SN-abc123",
        "holder": { "email_hash": "5d41...", "name_present": true },
        "voided": false,
        "image_variant": null,
        "url": "https://passmint.com/p/abc123",
        "download_url": "https://passmint.com/p/abc123/download",
        "google_wallet_url": null
      }
    }
  },
  "source": { "platform": "apple", "unit": "device", "confidence": "exact" },
  "previous_attributes": null
}

data.object.pass is a snapshot of the pass when the event was sent. Holder details are minimized to an email hash and a name-present flag; fetch GET /v1/passes/:id for the full pass. image_variant is the template image variant the pass renders with, or nullfor the template's own images (see Pass images). previous_attributes is always null for now.

pass.update_failed fires when an update or void was attempted and a wallet platform refused it: Apple Wallet pushes refused for a reason other than the holder deleting the pass, or a Google Wallet update error. When the failure may clear by itself, Passmint retries first and the event fires only if the retries run out (about 38 hours); failures that will not clear fire straight away. It does not fire when there was nobody to tell, such as a pass with no installed devices or one not saved to Google Wallet. You get one event per failing platform, and the change itself is still saved. It adds data.object.delivery_failure, and subscriptions to "*" receive it:

{
  "type": "pass.update_failed",
  ...
  "data": {
    "object": {
      "pass": { "id": "pass_xxx", ... },
      "delivery_failure": {
        "platform": "apple",
        "reason": "403 InvalidProviderToken",
        "failed": 1,
        "devices": 2,
        "context": "update"
      }
    }
  }
}

platform is apple or google. failed counts refused pushes (a number) and devices counts installed devices. devices is null for Google, and when an Apple pass could not be re-signed on a void. context is update or void.

Headers sent with each delivery:

  • Content-Type: application/json
  • passmint-signature: t=<unix_timestamp>,v1=<hmac_sha256>
  • passmint-event-type: pass.added_to_wallet
  • User-Agent: passmint-webhooks/1.0
04

Verifying signatures

Every delivery is signed with HMAC-SHA256 using the webhook secret returned at creation time. The passmint-signature header contains a timestamp and signature you should verify before trusting the payload.

The Node SDK handles this for you via webhooks.constructEvent:

import { Passmint } from "@passmint/node"

const passmint = new Passmint({
  apiKey: process.env.PASSMINT_API_KEY!,
})

// In your webhook handler
const event = passmint.webhooks.constructEvent(
  rawBody,
  request.headers["passmint-signature"]!,
  process.env.PASSMINT_WEBHOOK_SECRET!,
)

switch (event.type) {
  case "pass.added_to_wallet":
    // Handle install
    break
  case "pass.removed":
    // Handle removal
    break
}

The default tolerance is 5 minutes. Signatures older than that are rejected to prevent replay attacks.

05

Retries

Failed deliveries are retried up to 7 times with exponential backoff:

  • Immediate
  • 30 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 12 hours
  • 24 hours

After 7 failed attempts the delivery status becomes dead. Your endpoint should return a 2xx status within 10 seconds: anything else is treated as a failure.

06

Managing webhooks

Full CRUD is available on the /v1/webhooks resource:

  • GET /v1/webhooks: list all webhooks
  • GET /v1/webhooks/:id: retrieve a single webhook
  • PATCH /v1/webhooks/:id: update url, events, enabled, or description
  • DELETE /v1/webhooks/:id: permanently delete a webhook and its delivery history
07

Monitoring deliveries

Inspect recent delivery attempts for any webhook with GET /v1/webhooks/:id/deliveries. Each delivery record includes the HTTP status, response body (truncated to 2,000 characters), and timestamps, which helps when debugging failing endpoints.

Delivery statuses:

  • pending: queued, not yet attempted
  • delivered: your endpoint returned a 2xx
  • failed: attempt failed, will be retried
  • dead: all retry attempts exhausted