Webhooks
Signed events pushed to your HTTPS endpoints when things happen in your workspace.
Register endpoints on /developer. Each endpoint gets a signing secret (whsec_…, shown once). Internal and private-network URLs are rejected (SSRF-guarded); a test ping is one click.
Events
| Event | Fires when | Data |
|---|---|---|
opportunity.pursuit.set | decision made | { "signalId", "status": "pursuing" | "watching" | "passed" } |
opportunity.pursuit.cleared | decision cleared | { "signalId" } |
ping | test button | A sample payload to verify your receiver. |
Delivery format
POST https://your-app.com/hooks/winlayer
content-type: application/json
x-winlayer-event: opportunity.pursuit.set
x-winlayer-signature: sha256=8f2a… // HMAC-SHA256 of the raw body
{ "id": "1d8ca81f-…",
"event": "opportunity.pursuit.set",
"data": { "signalId": "43eeed69-…", "status": "pursuing" },
"sentAt": "2026-07-24T12:00:00.000Z",
"attempt": 1 }Verifying the signature
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, header: string, secret: string): boolean {
const expected = "sha256=" +
createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
// verify(req.rawBody, req.headers["x-winlayer-signature"], process.env.WINLAYER_WEBHOOK_SECRET)Compute the HMAC over the raw request body, before any JSON parsing. Delivery is at least once: transient failures are retried up to five attempts, so deduplicate with the stable payload id. Retry state and terminal failures are visible on /developer.