Two different problems: nothing arrives at all, or things arrive but fail verification. Work out which you have first - the causes don't overlap.
Nothing is arriving
- Is the subscription active? Check Settings → Webhooks. A disabled endpoint receives nothing.
- Is it subscribed to the right event? Ticking
pass.createdwon't deliver installs. Check the event list on the subscription against what you're expecting. - Did the event actually happen? If you're waiting on
pass.installed, someone has to have genuinely added the pass to a wallet. Check the pass's status - opening the install page isn't installing. - Is your endpoint publicly reachable over HTTPS?
localhostand anything behind a VPN or firewall can't be reached. Use a tunnel such as ngrok for local development. - Are you in the right account? A subscription belongs to one account. See Working across multiple accounts.
Steps 3 and 4 account for most cases.
Confirming your endpoint is reachable
Point a subscription at a request-inspection service - webhook.site or RequestBin - and issue a pass.
- Events arrive there - the problem is your receiver, not us.
- Nothing arrives there either - the subscription or event configuration is wrong. Back to step 1.
That single test splits the problem in half and takes a minute.
Signature verification is failing
Almost always one of two things.
You're verifying the parsed body rather than the raw body.
The signature is computed over the exact bytes we sent. If your framework parses the JSON and you re-serialise it to verify, the bytes differ - key order, whitespace, number formatting - and it will never match, no matter how correct your code looks.
You need the raw request body, before any JSON parsing. Most frameworks parse automatically, and you have to opt out for the webhook route specifically.
This is the single most common webhook integration problem, and it's invisible from reading the code - your HMAC implementation can be perfect and still never match.
You're using the wrong secret.
Each subscription has its own signing secret, shown once when created. If you have several subscriptions, check you're using the one belonging to the endpoint receiving the request. If the secret was rotated, deploy the new one.
There's full example code in Webhooks.
Events arrive but late, or twice
Late: if your endpoint returned a non-2xx or timed out, we retry with exponential backoff. A delivery that failed twice arrives a few minutes later. Check your own error logs for the earlier attempts.
Twice: delivery is at-least-once by design. Deduplicate on the X-Webhook-Id header, and make your handler idempotent.
Retries have stopped
We retry up to six times over roughly fifteen minutes, then stop. If your endpoint was down for longer, those events are gone - we don't replay them later.
If you've had an outage, reconcile by listing passes over the API rather than waiting for redelivery. See Creating passes over the API.
Intermittent failures
Usually a slow handler. Requests time out after 10 seconds.
If your handler calls other services before responding, it will occasionally exceed that, get retried, and do the work twice.
The fix is always the same: acknowledge immediately with 200, queue the work, process it separately.
An event you subscribed to never fires
Check the event list in Webhooks - it documents exactly when each of the four events fires.
Worth knowing: pass.removed is reliable on Apple Wallet but not on Google, which doesn't consistently report deletions. If you're seeing fewer removals than you expect on Android, that's why.
Where to go next
- Webhooks - setup and signature verification in full.
- API errors and rate limits.