Why are webhooks not arriving?
Open the webhook's delivery log first: it shows every attempt with its HTTP status and error. No delivery at all means a filter excluded the event, or the webhook did not exist yet when the event happened. Deliveries marked pending or failed mean your backend answered something other than HTTP 200, timed out after 60 seconds, or could not be reached.
Look at the deliveries#
Shell
curl -s -H "Authorization: Bearer $SECRET_KEY" \
"https://revenuedot.example.com/v2/projects/$PROJECT_ID/webhooks/$WEBHOOK_ID/deliveries?status=failed"The dashboard's Webhooks page shows the same log. For a project-wide view, GET /v2/projects/{project_id}/setup_health has a webhooks block with the delivered share over the last 24 hours and every webhook whose last attempt failed.
To check the wiring end to end, send a TEST event:
Shell
curl -s -X POST -H "Authorization: Bearer $SECRET_KEY" \
https://revenuedot.example.com/v2/projects/$PROJECT_ID/integrations/webhooks/$WEBHOOK_ID/testNo delivery at all#
- The environment filter excludes it. A webhook with
environment: "production"never receives sandbox or Test Store events. Test Store purchases are always sandbox. Setenvironmenttonullfor both. - The event type filter excludes it. If
event_typesis set, only those types are sent. - The app filter excludes it. If
app_idis set, events from other apps are skipped. - The webhook did not exist yet. Deliveries are queued when the event happens, for the webhooks that exist then. A webhook you add later does not get older events.
- No event happened. An imported customer produces no events unless the import ran with
--emit-events. A purchase RevenueDot has not seen, reported only by a store notification, produces nothing unlesstrack_new_purchasesis on. See Why are store notifications not arriving? - The event type is never sent yet. RevenueDot accepts
TEMPORARY_ENTITLEMENT_GRANT,VIRTUAL_CURRENCY_TRANSACTION,INVOICE_ISSUANCE,EXPERIMENT_ENROLLMENT,PURCHASE_REDEEMEDandSUBSCRIBER_ALIASin filters, but does not produce them yet. See Known issues.
Deliveries that fail#
- Your backend answers something other than 200. Only 200 counts as delivered. A 201, 204 or redirect is a failure and is retried. Answer 200 as soon as you have stored the event, and do slow work afterwards.
- Your backend is too slow. RevenueDot waits 60 seconds, then counts the attempt as failed.
- Signature checks reject the request. Verify
X-RevenueCat-Webhook-Signatureagainst the raw request body, not re-serialized JSON, with thewhsec_secret of this webhook. The header ist=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">. A new signature is made for every attempt. See Webhooks for verification code. - The
Authorizationheader does not match. If you setauthorization_headeron the webhook, RevenueDot sends it exactly as stored. Compare it with what your backend expects. - The server cannot reach the URL. A self-hosted container cannot reach
localhoston your computer. Usehttp://host.docker.internal:<port>on Mac and Windows, and on Linux addextra_hosts: ["host.docker.internal:host-gateway"]to the service.
Retries and timing#
- The server sends due webhooks from a background job every 30 seconds, and right after a purchase.
- A failed attempt is retried after 5, 10, 20, 40 and 80 minutes. After that the delivery is marked
failed. This matches RevenueCat's schedule (RevenueCat docs). - Retry by hand from the dashboard, or with
POST /v2/projects/{project_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry. - Delivery is at least once. Deduplicate on
event.id.
A real delivery, captured from a local run, looks like this:
Text
POST /api/webhooks/revenuedot
content-type: application/json
user-agent: RevenueDot-Webhooks/1.0
x-revenuecat-webhook-signature: t=1790800914,v1=5e6c0809f9b7b24f7ae36f4744b3b04868222411db4b24c60e6e390c5e9c495f
{"event":{"id":"E6BD2240-52BB-444A-8C96-AA85468119C6","type":"INITIAL_PURCHASE","store":"PROMOTIONAL","app_user_id":"user_9","environment":"PRODUCTION","product_id":"rc_promo_pro_weekly","period_type":"PROMOTIONAL", "...": "..."},"api_version":"1.0"}