Verifying signatures
Verify webhook deliveries with the X-JE-Signature HMAC-SHA256 header and the timestamp, and reject replayed requests using a recommended five minute window.
Each delivery carries these headers:
X-JE-Timestamp: <unix seconds>: when the delivery was signed.X-JE-Signature: sha256=<hex>: an HMAC-SHA256, keyed by your endpoint's signing secret (whsec_…), over the string${timestamp}.${rawBody}(the timestamp, a dot, then the exact bytes of the request body).X-JE-Event-Id: evt_…: the eventid, the same on every retry. Use it to deduplicate.
Binding the timestamp into the signature lets you reject replays: check that the timestamp is recent (a 5-minute window is recommended) before trusting the payload. Each attempt, including retries, is re-signed with a fresh timestamp.
const crypto = require('crypto');
function verify(rawBody, tsHeader, sigHeader, secret, toleranceSec = 300) {
const ts = parseInt(tsHeader, 10);
if (!Number.isFinite(ts)) return false;
if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false; // replay window
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${ts}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(sigHeader || ''), b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Compute the HMAC over the exact bytes received (don't re-serialize the JSON) and
prepend ${timestamp}. before hashing. The comparison must be constant-time.
Send yourself a signed test delivery
To check a receiver before any real event reaches it, sign a body with your
endpoint's secret the same way and post it to the receiver. Both samples read the
secret from WEBHOOK_SECRET and send to a receiver at
http://localhost:3000/webhooks/cd; change that to yours.
BODY='{"id":"evt_test","type":"connection.live","domain":"www.acme.test"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //')
curl -X POST http://localhost:3000/webhooks/cd \
-H "Content-Type: application/json" \
-H "X-JE-Timestamp: $TS" \
-H "X-JE-Signature: sha256=$SIG" \
-H "X-JE-Event-Id: evt_test" \
-d "$BODY"Your receiver should answer 2xx for this request, and reject it once you change
one byte of the body or send a timestamp older than your window. For real, signed
deliveries without a real domain, register the endpoint on a
test mode application.
Webhook events and delivery
Register HTTPS endpoints to receive signed webhook events as connections progress, with the event catalog, the payload envelope and how delivery retries work.
MCP server
The CustomDomain™ Model Context Protocol server: twenty-three tools an AI agent can call to search, connect and manage domains, plus scopes, transports and auth.