Stripe webhook replay tolerance is left at the default or set to a bounded value (600 seconds or less)
Why it matters
Stripe signs timestamp.payload and the SDK rejects events whose timestamp is outside a tolerance window, 300 seconds by default, so a captured request cannot be replayed hours later. The tolerance is the fourth argument to constructEvent(payload, header, secret, tolerance), and the failure is overriding it to make a local test or a clock-skew error go away: 0, a negative number, Infinity, or a huge literal. Passing 0 is the trap that matters in practice because several SDKs treat a falsy tolerance as "do not check the timestamp", and an assistant that was asked to "fix the timestamp outside tolerance error" will often disable the check rather than fix the server clock. With replay protection off, any signed event an attacker observes (a leaked log line, a proxy capture, a staging copy of production traffic) can be re-sent to re-run fulfillment or entitlement logic. This is CWE-294 (Authentication Bypass by Capture-replay) and CWE-345, and it is independent of whether the signature itself is verified: the signature stays valid forever once the window is disabled. Stripe documents the default tolerance and recommends against disabling timestamp checks.
Severity rationale
High because a disabled or enormous timestamp tolerance lets a captured, validly signed event be replayed indefinitely, so one intercepted `invoice.paid` or `checkout.session.completed` can be re-submitted later to re-trigger fulfillment, but it requires the attacker to have captured a real signed payload first.
Remediation
Remove the tolerance argument so the SDK default (300 seconds) applies, or pass an explicit value of 600 seconds or less. If you hit "Timestamp outside the tolerance zone" errors, fix the server clock (NTP) or make sure you are verifying the unmodified raw body; do not widen the window.
// default tolerance (300s) - preferred
event = stripe.webhooks.constructEvent(body, sig, secret)
// or an explicit bounded value
event = stripe.webhooks.constructEvent(body, sig, secret, 300)
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Stripe Webhooks & Billing Integrity audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- cwe · CWE-345 — Insufficient Verification of Data Authenticity
- cwe · CWE-294 — Authentication Bypass by Capture-replay
- owasp:2021 · A08 — Software and Data Integrity Failures
- external · stripe-webhooks-replay-attacks — Stripe docs: Protect against replay attacks (timestamp tolerance)
Taxons
History
- 2026-10-05·v1.0.0·Initial authoring — replay-tolerance disabled/oversized shapes; includes a detect-rg snippet for nested-paren constructEvent calls.·by stripe-billing-integrity-v1-build