n8n webhook auth is Basic, Header or JWT: verify Sume's HMAC yourself

n8n's Webhook node offers Basic, Header and JWT auth, none of which checks Sume's HMAC. Verify sume-v1 over timestamp.raw_body in code, 300 second window.

4 min readSume
All posts

n8n's built-in Webhook authentication cannot validate a Sume signature, because Sume signs the raw body with HMAC SHA-256 and sends the result in a header. So leave n8n auth for what it does well, and add a verification step that recomputes the HMAC and rejects bad or stale deliveries.

Below are the two schemes side by side and a verifier you can place in a Code node or a small service.

n8n authentication options

n8n Webhook node authentication, read 2026-10-05
OptionWhat it checks
Basic authA username and password credential you define
Header authA header name and value you define
JWT authA JSON Web Token credential
NoneNothing
Raw Body optionLets the node receive the body as raw data, which an HMAC check needs

What Sume sends

Sume webhook signature, Sume docs checked 2026-10-05
ItemValue
Signed string<timestamp>.<raw_body>, HMAC SHA-256
Headersx-sume-webhook-timestamp, x-sume-webhook-signature (sume-v1=<hex>), x-sume-webhook-secret-fingerprint
RotationThe signature header carries one sume-v1= entry per live secret, comma separated
Replay windowReject timestamps outside a tolerance; five minutes (300 s) is the documented default
SecretDashboard Webhooks tab, or GET /v1/webhooks/signing-secret with account:read

A verifier

This is plain Node. Check that your n8n deployment allows the crypto module in code steps before you rely on it, or run it in a tiny service in front of n8n. Feed it the raw body, not re-serialised JSON, because any change to the bytes breaks the signature. The Sume TypeScript SDK also exports a verifyWebhook helper.

import crypto from "node:crypto";

export function verifySume({ rawBody, timestamp, header, secret, tolerance = 300 }) {
  if (!secret) throw new Error("refusing to verify with an empty secret");
  const ts = Number(timestamp);
  if (!Number.isFinite(ts)) return false;
  if (Math.abs(Date.now() / 1000 - ts) > tolerance) return false;
  const digest = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");
  const want = Buffer.from(`sume-v1=${digest}`);
  const results = header.split(",").map((entry) => {
    const got = Buffer.from(entry.trim());
    return got.length === want.length && crypto.timingSafeEqual(got, want);
  });
  return results.includes(true);
}

Layering it with n8n auth

  • Keep Header auth on the Webhook node as a cheap first gate if you like, but never treat it as proof the body came from Sume.
  • Store the Sume signing secret in n8n credentials or an environment variable, never in the workflow JSON.
  • After verification, dedupe on job_id and return 2xx fast. Sume gives each attempt 10 seconds and tries up to 10 times.
  • When verification fails after rotation, compare x-sume-webhook-secret-fingerprint with the fingerprint in the dashboard rather than sending the secret anywhere.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume