My webhook isn't arriving

Nothing reaching your endpoint, or signature checks failing - the checks that find the cause, in order, and the two mistakes that account for most cases.

Updated 7/26/2026

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

    1. Is the subscription active? Check Settings → Webhooks. A disabled endpoint receives nothing.
    2. Is it subscribed to the right event? Ticking pass.created won't deliver installs. Check the event list on the subscription against what you're expecting.
    3. 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.
    4. Is your endpoint publicly reachable over HTTPS? localhost and anything behind a VPN or firewall can't be reached. Use a tunnel such as ngrok for local development.
    5. 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

More in Troubleshooting