Verify Entylink webhook signatures in Node.js
Implement secure webhook verification in Node.js using Entylink's `X-Entylink-Signature` header, the raw request body, and the signing secret returned when the webhook is created.
Start from the real implementation problem
Keep the setup explicit
A webhook created through `/v1/webhooks`
The `secret` value returned at creation time
A route handler that can read the raw request body
Store the signing secret when the webhook is created
The webhook creation response includes a one-time `secret`. Store it immediately in your own secrets manager or configuration store because you need it to verify future deliveries.
Read the raw request body, not a re-serialized object
The signature is calculated over the exact JSON body string. If you parse and re-stringify the payload first, verification can fail.
import { createHmac, timingSafeEqual } from "crypto";
export async function POST(request: Request) {
const signature = request.headers.get("x-entylink-signature") || "";
const body = await request.text();
const expected = `sha256=${createHmac(
"sha256",
process.env.ENTYLINK_WEBHOOK_SECRET!,
).update(body).digest("hex")}`;
const isValid =
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!isValid) {
return new Response("invalid signature", { status: 401 });
}
const payload = JSON.parse(body);
return Response.json({ ok: true, event: payload.event });
}Use the event header to route logic cleanly
Entylink includes `X-Entylink-Event` so you can branch cleanly by event type such as `filing.new`, `officer.appointed`, or `company.status_changed`.
Make the handler idempotent
Your webhook consumer should tolerate duplicate deliveries or replay logic. Persist a delivery identifier or event fingerprint before triggering downstream work.