verifyWebhook toleranceSeconds 0 turns off the Sume replay check

In @sume-com/sdk, toleranceSeconds defaults to 300 and 0 skips the timestamp check. A runnable test shows an hour-old signed delivery passing only at 0.

4 min readSume
All posts

In verifyWebhook from @sume-com/sdk, toleranceSeconds defaults to 300, and a value of 0 skips the timestamp check entirely. That makes 0 handy in a test and dangerous in production: a captured delivery with a valid signature would verify forever. Leave the option unset in your receiver, or set it to a number of seconds you can defend, and keep 0 for fixtures only.

What the option does

The signed text is the timestamp, a dot and the raw body, hashed with HMAC-SHA256. The helper enforces the replay window before it computes the HMAC, and it returns false instead of throwing.

verifyWebhook input, from the SDK docs read 2026-10-08
FieldNotes
bodyThe raw body: string, ArrayBuffer or typed array
headersHeaders, Map or plain object, case-insensitive
secretYour Sume webhook signing secret
toleranceSecondsReplay window, default 300, 0 skips the timestamp check

A test you can run

This script signs a body with a timestamp from an hour ago, using a dummy secret. It prints the verdict for the default window, an explicit 300, and 0. Expect false, false and true.

import { createHmac } from "node:crypto";
import { verifyWebhook } from "@sume-com/sdk";

const secret = "a".repeat(64); // test value only
const body = JSON.stringify({ event: "job.completed", job_id: "job_1" });
const ts = Math.floor(Date.now() / 1000) - 3600; // signed an hour ago
const sig = createHmac("sha256", secret).update(`${ts}.${body}`).digest("hex");
const headers = {
  "x-sume-webhook-timestamp": String(ts),
  "x-sume-webhook-signature": `sume-v1=${sig}`,
};

for (const toleranceSeconds of [undefined, 300, 0]) {
  const ok = await verifyWebhook({ body, headers, secret, toleranceSeconds });
  console.log(`tolerance ${toleranceSeconds ?? "default"}: ${ok}`);
}

Choosing a window

A replay window and a dedupe table do different jobs, and a receiver should have both.

  • Keep the default of 300 seconds unless your clocks drift. Fix the drift with NTP before you widen the window.
  • Do not use 0 in a staging environment that shares a secret with production.
  • Dedupe on job_id or request_id as well. The timestamp check stops old replays, not a fast duplicate.
  • A receiver that is down for longer than the window rejects the late retry, so Sume's redeliver endpoint is the recovery path, not a wider tolerance.

Why the window exists

A valid signature proves that Sume signed a body at some moment. It does not prove the delivery is new. Without a window, anyone who once captured a request, for instance from a proxy log, could send it again and your receiver would accept it. The timestamp header is part of the signed text, so an attacker cannot move it forward without breaking the signature. That is why the window check and the HMAC belong together.

If your fixtures need a fixed timestamp, sign them with the current time at test start, as the script above does, instead of switching the check off.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume