Verify webhook signatures
Webhooks tell your system when a record is created, updated, deleted, restored, purged, or nears expiry. Add an endpoint in Integrations → Webhooks or with the API, and keep its signing secret. Every delivery is a POST with a JSON body:
{
"id": "3f1c…",
"type": "document.expiring",
"created_at": "2026-10-31T01:00:00.000Z",
"organization_id": "…",
"resource_version": 7,
"data": { "id": "…", "title": "…", "end_date": "2026-11-15" }
}
Check the signature
Each request carries EverdeenDMS-Signature: t=<unix seconds>,v1=<hex>. Compute HMAC-SHA256 with your signing secret over the timestamp, a dot, and the raw request body, and compare it with every v1 value in constant time. For 24 hours after you rotate the secret, a second v1 signed with the previous secret is included. Reject timestamps more than 5 minutes old.
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = header.split(",").map((p) => p.split("="));
const t = parts.find(([k]) => k === "t")?.[1];
const sigs = parts.filter(([k]) => k === "v1").map(([, v]) => v);
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return sigs.some(
(s) =>
s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
}
Handle retries and ordering
- Answer with any 2xx status within 10 seconds. Anything else is retried 8 times: after 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 6 h, and 14 h. Then the delivery is marked dead; you can replay it.
- Deliveries can arrive more than once and out of order. Ignore an id you have already processed, and keep the highest resource_version per record.
- An endpoint that keeps failing for 3 days is turned off and the owner and admins are emailed.