Webhooks
Provider-verified callbacks for RevenueCat subscription events and Superwall paywall events.
Webhooks live at the top level (not under /client or /admin) because they're authenticated by provider-specific signatures, not by API keys or developer tokens. The project_id a given payload belongs to is supplied as a query-string parameter.
Both providers:
- Verify signatures with
timingSafeEqual(no length-based timing leaks). - Require the project to have an active integration for the provider with a configured
webhook_secret. - Rate-limit per project: 100 requests / second (bucket key =
project_id).
POST /webhooks/revenuecat
Receive and process a RevenueCat subscription event (initial purchase, renewal, cancellation, billing issue, etc.). Processing happens asynchronously — the handler verifies the request, queues the event for processing, and returns.
Request
| Piece | Where | Description |
|---|---|---|
project_id | query string | The Amba project the event belongs to. |
Authorization: Bearer <secret> | header | Must exactly equal the webhook_secret configured in the project's revenuecat integration. Constant-time compared. |
| JSON body | body | RevenueCat's standard WebhookEvent payload. Forwarded unmodified to the processor. |
Response 200
Retry semantics
RevenueCat treats any 2xx as "delivered" and never retries. Any 5xx triggers retries on RevenueCat's side. We return 500 if processing can't be queued so the event isn't silently dropped.
Errors
400 MISSING_PROJECT—project_idquery param missing.401 INVALID_SIGNATURE—Authorizationheader missing or wrong secret.404 NOT_CONFIGURED— project has no activerevenuecatintegration or nowebhook_secreton file.429— rate-limited (100/sec per project).500 WEBHOOK_PROCESSING_FAILED— processing could not be queued. RevenueCat will retry.
Called by RevenueCat — you can't invoke this directly. The curl below shows the expected shape for local testing.
Curl:
POST /webhooks/superwall
Receive a Superwall paywall event. If the payload includes event + user_id, Amba writes a single engagement event (event_name = "superwall_<event>", full payload stored in properties). No follow-on processing fires.
Request
| Piece | Where | Description |
|---|---|---|
project_id | query string | Amba project. |
x-superwall-signature | header | Hex-encoded HMAC-SHA256 of the raw request body using the project's webhook_secret. Verified BEFORE JSON parsing so malformed/forged payloads never reach any side effects. |
| JSON body | body | Superwall event object. Minimum fields the handler reads: event (string), user_id (app user id). Any other fields are stored verbatim in the engagement event's properties. |
Response 200
Retry semantics
Superwall retries on non-2xx. The handler returns 200 even if the engagement-event INSERT fails (non-fatal, logged) to avoid replay loops for write-level issues.
Errors
400 MISSING_PROJECT—project_idmissing.400 INVALID_BODY— body is not valid JSON.401 INVALID_SIGNATURE—x-superwall-signaturemissing or doesn't match the HMAC of the raw body.404 NOT_CONFIGURED— project has no activesuperwallintegration or nowebhook_secret.429— rate-limited.
Called by Superwall — you can't invoke this directly. The curl below shows the expected shape for local testing.
Curl:
Configuring the secret
Both providers take their shared secret from the active integration on your project. Use POST /admin/projects/:projectId/integrations to create or update the integration; supply the secret as config.webhook_secret.