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.
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 them.
pass.issued— A new pass was signed and issued.pass.add_intent— A holder took a step toward adding the pass — downloaded the file or tapped 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 was pushed to installed wallets.pass.update_delivered— A device pulled the updated pass (Apple-confirmed delivery).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.
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 event types.
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.
Payload format
Every delivery sends a JSON body with the event metadata and the affected pass:
{
"id": "psEvnt_abc123",
"type": "pass.added_to_wallet",
"created_at": "2026-04-16T12:00:00.000Z",
"data": {
"pass": {
"id": "pass_xxx",
"short_id": "abc123",
"mode": "live",
"holder_email": "alice@example.com",
"holder_name": "Alice Johnson",
"field_values": { "seatNumber": "A12" },
"voided": false
},
"event": {
"type": "installed",
"metadata": null
}
}
}Headers sent with each delivery:
Content-Type: application/jsonpassmint-signature: t=<unix_timestamp>,v1=<hmac_sha256>passmint-event-type: pass.added_to_walletUser-Agent: passmint-webhooks/1.0
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.
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.
Managing webhooks
Full CRUD is available on the /v1/webhooks resource:
GET /v1/webhooks— list all webhooksGET /v1/webhooks/:id— retrieve a single webhookPATCH /v1/webhooks/:id— updateurl,events,enabled, ordescriptionDELETE /v1/webhooks/:id— permanently delete a webhook and its delivery history
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 — useful for debugging failing endpoints.
Delivery statuses:
pending— queued, not yet attempteddelivered— your endpoint returned a 2xxfailed— attempt failed, will be retrieddead— all retry attempts exhausted