Network failures don't lose data
Eight retries + DLQ + replay covers virtually every transient subscriber failure. You never have to ask 'did we miss an event?'
HMAC-SHA256 signed deliveries, exponential backoff with up to 8 attempts, dead-letter queue with one-click replay, per-subscriber circuit breaker plus bulkhead. We treat your subscribers like the unreliable network endpoints they actually are.
Overview
Subscribe to event types (pricing.computed, ratesheets.activated, locks.lifecycle, etc.). Each delivery is signed with HMAC-SHA256, retried up to 8 times with exponential backoff, and dropped to the DLQ on terminal failure. The DLQ is queryable and replayable through the public API or the portal. Per-subscriber circuit breakers and bulkheads protect you from one bad subscriber blocking the rest.
Each delivery carries X-RateStack-Signature (sha256=<hex>) and X-RateStack-Timestamp. Constant-time verify; reject deliveries older than 5 minutes.
Up to 8 attempts: 30s, 1m, 2m, 5m, 15m, 30m, 1h, 4h. Subscriber returns 2xx → mark delivered. Returns 5xx or timeout → retry.
After 8 failures, the delivery moves to DLQ. List, inspect, and replay through /internal/v1/dlq or the portal — replays are themselves rate-limited so they don't stampede.
If your endpoint sustains failures, the breaker opens and we stop hammering. New deliveries queue; the breaker probes periodically and closes when you recover.
Each subscriber has a bounded outbound concurrency budget. One slow subscriber cannot consume the whole worker pool.
Every delivery carries a stable eventId. Use it as your idempotency key; we will deliver the same eventId again on retry, never with a different payload.
How it works
Numbered steps from input to output. Each step maps to a specific subsystem you can inspect via OpenTelemetry.
POST /v1/webhooks with target URL, event types, and an optional secret (auto-generated if omitted). Secret is shown once at creation; it is encrypted at rest with the master key.
When pricing.computed, ratesheets.activated, locks.created, etc. fire on NATS, webhook-service consumes the event and creates a delivery row keyed by (subscription, eventId).
Worker computes HMAC-SHA256 over timestamp + body, sets headers, POSTs to your URL with a 30s timeout. Logs the response code, latency, and body sample.
On 5xx or timeout, the delivery is requeued at the next backoff slot. Up to 8 attempts. Successes settle to a delivered row with correlationId.
After 8 failures, the delivery moves to DLQ. We do not silently drop. List, inspect, replay — your call.
Validate the signature, the timestamp window (5 min), and the eventId for idempotency. Reject anything else.
Hands on
Live TypeScript sample — copy, paste, ship.
// Verify a webhook delivery in your subscriber
import crypto from "node:crypto";
export function verify(
body: string,
headers: { "x-ratestack-signature": string; "x-ratestack-timestamp": string },
secret: string,
) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${headers["x-ratestack-timestamp"]}.${body}`)
.digest("hex");
// constant-time compare
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(headers["x-ratestack-signature"].replace(/^sha256=/, "")),
);
}Why this matters
Eight retries + DLQ + replay covers virtually every transient subscriber failure. You never have to ask 'did we miss an event?'
The bulkhead prevents your slow integration from taking the whole webhook fleet down. Other subscribers stay healthy.
When a subscriber rolls a bug fix, you replay the affected window from DLQ. No backfill scripts, no support escalation.
Frequently asked
Verifiers should reject deliveries older than 5 minutes by clock skew tolerance. The timestamp is in the header; build your verifier to enforce it.
Yes. The portal supports rotation with a 24-hour overlap window during which both the old and new secret produce valid signatures.
pricing.computed, pricing.eligibility.computed, ratesheets.activated, locks.created, locks.extended, locks.cancelled, locks.funded, locks.expired, loans.imported. The full list is in the OpenAPI spec.
At least once, ordered per subscription, with a stable eventId for dedup. Implement idempotency on your side keyed by eventId.
Related capabilities
Ready to see it on your data?
We'll spin you a sandbox, load your actual ratesheets, and walk you through this capability against your top scenarios.