Spectra
Browse docs

webhooks

Replay protection

How Spectra prevents a captured webhook from being replayed against your account.


Replay protection on a signed webhook stops two attacks:

  1. An attacker who captured a valid request can't resend it to fire the same order again.
  2. A misbehaving sender can't accidentally resend stale messages after network blips.

The mechanism

Each request carries X-Spectra-Timestamp. The worker enforces:

  • Freshness window — 5 minutes by default. Requests with timestamps older than now − 5 min are rejected with 401.
  • Nonce cache — the worker caches (secret_id, timestamp, body_hash) for the freshness window. A second request with all three matching is rejected with 409 Conflict. The cache is in-memory only (no Postgres write per request) so it's cheap.

Together these mean: a captured request is replayable for at most 5 minutes, and only if the attacker wins a race against your original delivery.

Tuning the window

Profile → Webhooks → secret → Advanced → Freshness window.

1 min   →  strict; recommended for low-latency networks
5 min   →  default
15 min  →  loose; only if your sender has unpredictable lag

Going below 1 minute is risky — clock skew between sender and the worker can cause genuine requests to fail freshness checks.

What about idempotency?

Idempotency (client_id) and replay protection solve different problems. Idempotency lets the same sender retry safely. Replay protection stops a different sender from re-using a captured request. Use both.

Symptoms of clock skew

If you see legitimate requests rejected with timestamp out of range, your sender's clock is drifting. Check NTP. The error includes the worker's now-time so you can compute the delta.