Sume webhook fails the 300-second window: find the clock drift

A valid Sume signature still fails if your server clock is more than five minutes off. Tell drift from a bad secret with a small Python check.

4 min readSume
All posts

A Sume webhook delivery is signed over <timestamp>.<raw_body> and carries the timestamp in x-sume-webhook-timestamp. A receiver should reject a delivery whose timestamp is outside a replay window; the Sume docs suggest five minutes, and verifyWebhook in the SDK defaults toleranceSeconds to 300. If your host clock is off by more than that, every delivery fails even though the secret is correct.

The symptom looks like a secret problem, so people rotate secrets and make things worse. The fix is to separate the two failures in your handler and log which one happened.

Tell the two failures apart

Compute the skew before you compare signatures. A positive skew means your clock is ahead of the timestamp Sume sent; negative means behind. The sample returns a distinct reason for each case.

import hashlib, hmac, time

def check(body, headers, secret, now=None, tolerance=300):
    if not secret:
        raise ValueError("empty signing secret")
    ts = int(headers["x-sume-webhook-timestamp"])
    skew = (time.time() if now is None else now) - ts  # > 0: your clock is ahead
    if abs(skew) > tolerance:
        return f"rejected: timestamp {skew:+.0f}s from your clock (limit {tolerance}s)"
    want = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    sigs = [p.removeprefix("sume-v1=") for p in headers["x-sume-webhook-signature"].split(",")]
    ok = any(hmac.compare_digest(want, s) for s in sigs)
    return "ok" if ok else "rejected: signature mismatch (not a clock problem)"

secret, body, ts = "whsec_test", b"{}", 1_790_000_000
sig = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
h = {"x-sume-webhook-timestamp": str(ts), "x-sume-webhook-signature": f"sume-v1={sig}"}
print(check(body, h, secret, now=ts + 2))     # ok
print(check(body, h, secret, now=ts + 420))   # clock 7 minutes ahead
print(check(body, h, secret, now=ts - 420))   # clock 7 minutes behind

What to do about it

  • Sync the host clock with your operating system time service, then watch the skew in your logs for a day.
  • Do not set toleranceSeconds to 0 to silence it. In the SDK, 0 skips the timestamp check, which also removes your replay protection.
  • If the signature mismatches with a sane clock, compare x-sume-webhook-secret-fingerprint with the fingerprint shown next to the secret in the dashboard. Neither side needs to send the secret itself.
  • After you fix the clock, redeliver missed jobs with POST /v1/jobs/{id}/webhook/redeliver; each redelivery gets a fresh timestamp and signature.

Containers and CI

A container shares the host kernel clock, but a paused virtual machine or a laptop that slept can resume minutes behind. If drift appears only in one environment, suspect that host first. Keep the injectable now parameter in the verifier so tests can pin a clock instead of sleeping.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume