Stripe tolerance 0 disables replay checks; Sume keeps a 300 s window

Stripe's default webhook tolerance is 5 minutes and 0 turns the check off. Sume's documented window is 300 seconds. Never set zero; reject stale timestamps.

4 min readSume
All posts

Never disable the replay check. Stripe's default tolerance is five minutes and setting it to 0 turns the check off, and Sume documents the same five minutes as 300 seconds. A signature proves who sent a body; only the timestamp window stops an attacker from re-sending an old valid delivery.

Here are the two documented windows and a short guard that refuses a zero tolerance.

The two windows

Replay protection, Stripe read 2026-10-05, Sume docs checked 2026-10-05
ItemStripeSume
Default tolerance5 minutes300 seconds (5 minutes), 'a reasonable default'
ZeroDisables the recency checkNot offered by the docs; the sample verifier takes a toleranceSeconds parameter
Signed contentTimestamp plus payload<timestamp>.<raw_body>, HMAC SHA-256
Secret rotationRoll overlap up to 24 hoursThe header carries one sume-v1= entry per live secret
RetriesLive mode up to 3 days10 attempts, 30 s apart, then redeliver

A caution about retries

Sume re-signs on redeliver with a fresh timestamp, so a legitimate late delivery still passes a 300 second window. Retries are different: they happen about 30 seconds apart, well inside the window. That is why the window can stay short without breaking delivery. You do not need a wide tolerance to cope with retries, so a zero or a very large value has no operational reason.

Guard code

Make tolerance a required positive number and fail loudly on an empty secret.

export function assertFresh(timestampSeconds, tolerance = 300, now = Date.now() / 1000) {
  if (!(tolerance > 0)) {
    throw new Error("tolerance must be greater than zero");
  }
  const ts = Number(timestampSeconds);
  if (!Number.isFinite(ts)) return false;
  return Math.abs(now - ts) <= tolerance;
}

export function requireSecret(secret) {
  if (!secret) throw new Error("webhook secret is empty");
  return secret;
}

Operational notes

  • Keep server clocks on NTP. A drifting clock turns a tight window into false rejections.
  • Log rejections with the received timestamp, never with the secret.
  • Dedupe on the event or job id as well. A fresh timestamp is not the same as a new event, since Sume redeliver sends the same job_id again.
  • Both services can send during a secret rotation, so accept any matching signature entry.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume