Skip to content
RateStack
Capability · Webhooks

Webhooks that survive a bad day at your subscriber.

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

What it is, in one paragraph

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.

  • HMAC-SHA256 signed

    Each delivery carries X-RateStack-Signature (sha256=<hex>) and X-RateStack-Timestamp. Constant-time verify; reject deliveries older than 5 minutes.

  • Exponential backoff

    Up to 8 attempts: 30s, 1m, 2m, 5m, 15m, 30m, 1h, 4h. Subscriber returns 2xx → mark delivered. Returns 5xx or timeout → retry.

  • Dead-letter queue

    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.

  • Circuit breaker per subscriber

    If your endpoint sustains failures, the breaker opens and we stop hammering. New deliveries queue; the breaker probes periodically and closes when you recover.

  • Bulkhead per subscriber

    Each subscriber has a bounded outbound concurrency budget. One slow subscriber cannot consume the whole worker pool.

  • Idempotency on your side

    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

The pipeline, end to end.

Numbered steps from input to output. Each step maps to a specific subsystem you can inspect via OpenTelemetry.

  1. 1

    Create a subscription

    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.

  2. 2

    Event fires

    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).

  3. 3

    Sign and POST

    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.

  4. 4

    Retry with backoff

    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.

  5. 5

    DLQ on terminal failure

    After 8 failures, the delivery moves to DLQ. We do not silently drop. List, inspect, replay — your call.

  6. 6

    Verify on your end

    Validate the signature, the timestamp window (5 min), and the eventId for idempotency. Reject anything else.

Hands on

Verify a webhook delivery in Node.js

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

The pain it removes.

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?'

Bad subscribers don't block good ones

The bulkhead prevents your slow integration from taking the whole webhook fleet down. Other subscribers stay healthy.

Replay is operational

When a subscriber rolls a bug fix, you replay the affected window from DLQ. No backfill scripts, no support escalation.

Frequently asked

Direct answers, no marketing spin.

What's the timestamp tolerance?

Verifiers should reject deliveries older than 5 minutes by clock skew tolerance. The timestamp is in the header; build your verifier to enforce it.

Can I rotate the secret?

Yes. The portal supports rotation with a 24-hour overlap window during which both the old and new secret produce valid signatures.

What event types are available?

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.

Are webhooks delivered exactly once?

At least once, ordered per subscription, with a stable eventId for dedup. Implement idempotency on your side keyed by eventId.

Ready to see it on your data?

Wire webhooks that survive a bad day at your subscriber. up to your real workflow.

We'll spin you a sandbox, load your actual ratesheets, and walk you through this capability against your top scenarios.

Webhooks — HMAC-signed, retried, DLQ, replayable | RateStack