Webhooks
Signature verification
Verify that a webhook request genuinely came from Emfas using the HMAC signature.
Every delivery is signed with your endpoint's secret so you can confirm it came from Emfas and wasn't tampered with. Always verify the signature before acting on a request.
The signature header
X-Emfas-Signature: t=1717406504,v1=3f8a1c...e9t— the Unix timestamp (seconds) when Emfas signed the request.v1— the hex-encoded HMAC-SHA256 signature.
How to verify
- Read the raw request body — verify against the exact bytes received, before any JSON parsing or re-serialization.
- Build the signed payload by concatenating the timestamp, a
., and the raw body:{t}.{body}. - Compute
HMAC-SHA256(secret, signedPayload)and hex-encode it. - Compare it to
v1using a constant-time comparison. - Optionally, reject requests whose timestamp
tis too old (e.g. more than 5 minutes) to limit replay.
import crypto from "node:crypto"
export function verifyEmfasSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
const { t, v1 } = parts
if (!t || !v1) return false
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex")
// Constant-time comparison
const a = Buffer.from(expected)
const b = Buffer.from(v1)
if (a.length !== b.length) return false
if (!crypto.timingSafeEqual(a, b)) return false
// Optional replay protection (5 minute tolerance)
const age = Math.abs(Date.now() / 1000 - Number(t))
return age < 300
}Verify against the raw body bytes. If your framework parses JSON before you can read the raw body, configure it to expose the original payload — re-serializing the parsed object can change byte order or whitespace and break verification.
Rotating the secret
You can rotate an endpoint's secret in Settings → Emfas API → Webhooks. Rotation issues a new secret immediately and invalidates the old one, so update your verification configuration as part of the rotation.