Webhooks
How inbound webhooks authenticate — RevenueCat Bearer token, Superwall HMAC-SHA256, and the generic retry contract.
Amba accepts inbound webhooks from third-party platforms on /webhooks/:provider?project_id=.... Every webhook is signature-verified before the body is parsed, using a constant-time comparison so timing attacks can't probe the secret. Unverified requests get 401.
Today there are two inbound webhook providers: RevenueCat (subscription events) and Superwall (paywall events). Each has its own auth mechanism mirroring the provider's wire format.
RevenueCat
RevenueCat authenticates webhooks with a bearer token you choose at integration-configure time.
Endpoint
The secret is what you passed as config.webhook_secret to POST /admin/integrations.
Verification (server-side)
Retry contract
On success Amba returns 200 { "data": { "received": true } }. If background processing fails, the endpoint returns 500 so RevenueCat retries per its webhook contract — silent event loss is worse than a visible failure.
Superwall
Superwall signs the raw body with HMAC-SHA256 using a shared secret, and sends the hex digest in x-superwall-signature.
Endpoint
Verification (server-side)
Reading the raw body before JSON parsing is critical — HMAC validates the bytes on the wire, not a re-serialized object. Parsing first would produce a different digest and invalidate every request.
Engagement mirror
Paywall events that include user_id + event are mirrored into your project's engagement_events table as paywall_<event> so paywall interactions feed into streaks, XP rules, and segment membership alongside first-party events. Delivery is deduplicated on the provider's event id, so a redelivered event is recorded once.
Rate limits
Each provider has a per-project, per-second rate limit (bucket keyed by project_id query param):
| Provider | Limit |
|---|---|
| RevenueCat | 100/sec |
| Superwall | 100/sec |
Bursts above the limit return 429. Providers batch events before delivery, so sustained load above this ceiling is more likely replay / abuse than normal traffic.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_PROJECT | 400 | project_id query param was not provided |
NOT_CONFIGURED | 404 | No active integration row for this project |
INVALID_SIGNATURE | 401 | Header missing or failed timingSafeEqual |
INVALID_BODY | 400 | Malformed JSON (after signature verified) |
WEBHOOK_PROCESSING_FAILED | 500 | Background processing could not accept the event (retry-safe for RC) |
Configuration
Both providers are configured identically, via the admin API:
The API responds with a webhook_url containing the exact URL to register in the provider dashboard. Push providers (apns, fcm) don't have webhooks and get no webhook_url — they only send.
Rotating a secret
The new secret takes effect immediately. Update the provider's webhook setting to match before rotating — mismatched secrets produce 401 INVALID_SIGNATURE until both sides agree.
Outbound webhooks
Amba can POST your engagement events to any HTTPS endpoint you own. Each subscription watches for a single event name and sends a signed delivery every time that event fires. You can register multiple subscriptions — one per event, or multiple per endpoint.
Register a subscription
event_name must match ^[a-z][a-z0-9_.-]{0,127}$. Use the same event names you track via Amba.track().
The response includes a secret — a 64-character hex string used to verify deliveries. Copy it immediately. It is shown exactly once; subsequent GET calls omit it. If you lose it, rotate the secret (see below).
Verify a delivery
Every delivery includes an X-Amba-Signature header with the format t=<unix_seconds>,v1=<hex_hmac>. The HMAC covers ${t}.${rawBody} (period-separated) so both the timestamp and the payload are bound to the signature.
Additional headers on every delivery:
| Header | Description |
|---|---|
X-Amba-Signature | t=<unix_seconds>,v1=<hex_hmac> — verify before parsing the body |
X-Amba-Idempotency-Key | Stable per-event UUID — use it to deduplicate retried deliveries |
X-Amba-Event-Name | The event name (same as event_name on the subscription) |
X-Amba-Event-Id | Source engagement event UUID — cross-reference with analytics |
X-Amba-Attempt | Delivery attempt counter (starts at 1) |
Return 2xx to acknowledge. Non-2xx responses trigger retries with exponential back-off. After all retries are exhausted the delivery enters dlq status and the subscription may enter degraded (circuit breaker).
Manage subscriptions
PATCH accepts event_name, target_url, and status ("active" or "disabled"). The degraded status is set automatically by the circuit breaker — re-enable by patching "status": "active".
Rotate the signing secret
Returns a new secret. Update your receiver to accept the new secret before rotating — deliveries in flight at the moment of rotation are still signed with the old secret.
Send a test delivery
Fires a synthetic delivery to the subscription's target_url without ingesting a real event or affecting the circuit-breaker state. Use it to confirm your endpoint is reachable before going live.
Delivery history
Delivery statuses: pending, delivered, failed, dlq. A delivery enters dlq after all retry attempts are exhausted. The detail response includes the full attempt log with HTTP status codes and error messages.
Replay a failed delivery
Re-enqueues a dlq or failed delivery with the original payload and idempotency key. Your receiver sees the same X-Amba-Idempotency-Key as the original delivery, so it can deduplicate correctly.
Next
- RevenueCat integration — end-to-end subscription sync.
- Superwall integration — end-to-end paywall wiring.
- Entitlements — what subscription state looks like in Amba.