INTEGRATION GUIDE·HMAC-SHA256 Signed

Webhooks

Webhooks stream real-time pass lifecycle updates directly to your servers. Each delivery is signed with a unique secret (whsec_…) to ensure authenticity and prevent replay attacks.

OpenAPI 3.0 SpecLaunch API Playground

Event catalogue

Passyn delivers structured events for state changes across passes, turnstiles, and payouts:

pass.purchased

Transaction succeeded; access record created.

Charge confirmations, fulfillment, CRM.
pass.activated

Access flipped to ACTIVE; countdown clock started.

Turnstile provisioning, SMS notification.
pass.expired

Access window ended or maximum entries consumed.

Turnstile lockout, win-back outreach.
pass.revoked

Manually revoked by merchant operator or dispute.

Immediate lockout, security alerts.
settlement.paid

Payout to connected merchant bank completed.

ERP reconciliation, bookkeeping.

Payload schema

Every payload contains the event type, timestamp, unique delivery ID, and full entity details:

event.json
{
"id": "evt_8f2c91d4a07b",
"event": "pass.expired",
"createdAt": "2026-06-11T09:00:00.000Z",
"data": {
"accessId": "acc_cm4abc1298ef",
"userId": "usr_cm4ghi3456jk",
"passId": "pass_day_gym_24h",
"passName": "Gym Day Pass",
"passType": "DAY",
"status": "EXPIRED",
"activatedAt": "2026-06-10T09:00:00.000Z",
"expiresAt": "2026-06-11T09:00:00.000Z",
"usageCount": 3
}
}

Signature verification

Headers include Passyn-Signature: t=<timestamp>,v1=<hmac>. Compute the HMAC-SHA256 signature over ${t}.${rawBody} and compare using constant-time equality:

verify-webhook.ts
import { createHmac, timingSafeEqual } from "crypto";
function verifyPassynWebhook(body: string, header: string, secret: string): boolean {
// Passyn-Signature: t=1781168400,v1=9f82...
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
// Enforce 5-minute replay attack tolerance window
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${body}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Delivery & retry guarantees

8-second timeout: Deliveries time out after 8 seconds; any non-2xx status triggers automatic backoff retries.

Idempotency: Handlers must be idempotent. Deduplicate using the event id.

Manual replay: Any failed delivery can be manually replayed directly from the developer console logs.