Delivery model
Events fire from Recurr’s billing layer in near real-time. Each event is delivered as an HTTP POST to your registered endpoint(s):| Header | Example | Purpose |
|---|---|---|
Content-Type | application/json | Payload format |
X-Recurr-Event-Id | evt_01HQX8K9M1P0R5N3Y2T7B4C6V | Globally unique event ID; use for idempotency |
X-Recurr-Event-Type | subscription.activated | Event type (see Events) |
X-Recurr-Schema-Version | v1 | Schema version (see Versioning) |
X-Recurr-Signature | t=1716386096,v1=5257a869... | HMAC-SHA256 signature (see Authentication) |
Payload structure
All events share a common envelope. Thedata field carries event-specific fields documented per type on the Events page.
Envelope field reference
| Field | Type | Description |
|---|---|---|
id | string | Globally unique event ID. Use for idempotency dedup. |
type | string | Event type. See Events for catalog. |
schema_version | string | Schema version. Current: v1. |
created_at | string (ISO 8601) | Event creation timestamp at Recurr. |
tenant.id | string | Customer’s Recurr tenant ID. |
tenant.name | string | Customer’s app / brand name. |
subscriber.id | string | Stable subscriber identifier across migrations + auth providers. |
subscriber.email | string | Raw email. Included for first-party destinations (your CS tool, BI, internal pipeline). |
subscriber.email_hashed | string | SHA256 hash of lower-cased trimmed email. Use for ad platforms / MMPs where raw PII shouldn’t flow. |
subscriber.created_at | string (ISO 8601) | When this subscriber first activated. |
subscription.* | object | Current subscription state at event time. Omitted for non-subscription events (e.g., ticket.submitted with no active sub). |
data | object | Event-specific payload. Documented per event type. |
PII handling
Two email fields are always present in the envelope:subscriber.email— raw emailsubscriber.email_hashed— SHA256 hash
- BI / CS / warehouse destinations receive both
- MMP / ad-platform destinations receive
email_hashedonly
Authentication
Every webhook is signed with HMAC-SHA256 using your endpoint’s signing secret.Signature header
t— Unix timestamp (seconds) when Recurr signed the payloadv1— HMAC-SHA256 of${t}.${rawBody}using your destination’s signing secret
Verification (Node.js)
Verification (Python)
Rejection policy
Reject the request (return 401 or 403) if any of the following:- Signature header missing or malformed
- HMAC verification fails
- Timestamp more than 5 minutes old (replay-attack protection)
- Timestamp more than 60 seconds in the future (clock skew protection)
Idempotency
Every event has a globally uniqueid (evt_*). Recurr may deliver the same event multiple times due to:
- Retries on 5xx responses
- Network failures during initial delivery
- Manual replay via the Replay API
Recommended pattern
Store receivedevt_* IDs in a dedup table (e.g., a webhook_events_received table keyed on event_id) for 30+ days. On each delivery:
- Check if
event_idexists in dedup table - If yes — return 200 immediately, skip re-processing
- If no — process the event, then insert
event_idinto dedup table
Retry policy
If your endpoint returns a non-2xx status code or fails to respond within 30 seconds, Recurr retries with exponential backoff:| Attempt | Delay after previous |
|---|---|
| Initial | — |
| Retry 1 | 1 minute |
| Retry 2 | 5 minutes |
| Retry 3 | 30 minutes |
| Retry 4 | 2 hours |
| Retry 5 | 12 hours |
| Retry 6+ | 24 hours, for up to 7 days total |
Specific status code handling
| Response | Treatment |
|---|---|
| 2xx | Success; no retry |
| 3xx | Treated as failure; retried |
| 4xx (except 408) | Treated as permanent failure; no retry (assumes config error) |
| 408 (Request Timeout) | Retried |
| 429 (Too Many Requests) | Retried; honors Retry-After header if present |
| 5xx | Retried |
Versioning
Webhook payloads are versioned viaschema_version in the envelope + the X-Recurr-Schema-Version header.
Versioning policy
- Additive changes (new optional fields) are non-breaking; deployed without version bump
- Breaking changes (renamed/removed fields, changed types, changed semantics) ship as a new schema version (
v2,v3, etc.) - New schema versions are announced 90 days before becoming default for new destinations
- Existing destinations stay pinned to their registered version until they explicitly migrate
Pinning a version
Specify your preferred schema version when registering a destination. Recurr emits events in that version’s shape until you migrate to a newer one. If no version is pinned, the destination receives the current default (v1 today).
Deprecation cadence
Older schema versions remain supported for 24 months after a newer default ships. Sunset notices are sent to the destination contact email at 12, 6, 3, and 1 month thresholds before removal.Rate limits
Webhook delivery is not rate-limited from Recurr’s side — events fire at the cadence of underlying subscription events. For API endpoints (e.g., Replay API, destination management), rate limits apply per Recurr API key:| Endpoint group | Limit |
|---|---|
| Read operations | 100 req/min |
| Write operations | 30 req/min |
| Replay invocations | 3 concurrent per tenant |
X-RateLimit-* headers communicate current usage.