Receive your first webhook

Webhooks push events to your server as they happen — an enrollment completed, a course published, a quiz passed. Each delivery is a signed POST; you verify the signature, then act on it. You’ll need an API key with the webhooks:manage permission and the variables from the overview.

01

Register an endpoint

Point Talent at a URL on your server and name the events you care about (or ["*"] for all of them). HTTPS is required — plain http is allowed only for localhost during development.

Shell
curl -X POST "$TALENT_API/webhook-endpoints" \
  -H "Authorization: Bearer $TALENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.example/talent/webhooks",
    "event_types": ["enrollment.completed"]
  }'

The response carries the signing secret — this is the one and only time it’s shown. Store it now; you sign with it below.

201 Created
{
  "id": "e1d2c3b4-a5f6-4788-9a0b-1c2d3e4f5a6b",
  "url": "https://your-server.example/talent/webhooks",
  "event_types": ["enrollment.completed"],
  "status": "active",
  "secret": "whsec_Zm9vYmFyYmF6cXV4Y29ycmVjdGhvcnNl",
  "…": "…"
}
02

Know what a delivery looks like

Every delivery is a POST with three webhook-* headers and a JSON body. The body is the same envelope for every event: an id, the type, an api_version, and an event-specific data object.

POST /talent/webhooks
webhook-id: e7a1…               (also the payload id)
webhook-timestamp: 1753376696    (unix seconds)
webhook-signature: v1,Base64Signature=
content-type: application/json

{
  "id": "e7a1b2c3-d4e5-4f60-8a91-b2c3d4e5f607",
  "type": "enrollment.completed",
  "created_at": "2026-07-24T17:11:36.204Z",
  "api_version": "2026-07",
  "data": {
    "enrollment_id": "…",
    "course_id": "…",
    "user_id": "…"
  }
}
03

Verify the signature

The signature is v1, followed by the base64 HMAC-SHA256 of {id}.{timestamp}.{body}, keyed by your secret. Compute the same value and compare in constant time. Two rules matter: verify over the raw request body (parsing then re-serializing will change the bytes and break the signature), and reject timestamps older than five minutes to stop replays.

TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_S = 300; // reject deliveries older than 5 minutes

/**
 * Verify a Talent webhook. Pass the raw request body (a string or Buffer),
 * the webhook-* headers, and the endpoint's signing secret.
 */
export function verify(secret: string, headers: Headers, rawBody: string): boolean {
  const id = headers.get("webhook-id");
  const timestamp = Number(headers.get("webhook-timestamp"));
  const signatureHeader = headers.get("webhook-signature");
  if (!id || !signatureHeader || !Number.isFinite(timestamp)) return false;

  // Replay protection.
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_S) return false;

  // The HMAC key is the base64-decoded part of the secret after "whsec_".
  const key = Buffer.from(secret.slice("whsec_".length), "base64");
  const expected =
    "v1," +
    createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`).digest("base64");

  // The header may carry several space-separated signatures (during secret
  // rotation); any one matching in constant time is a pass.
  const expectedBuf = Buffer.from(expected);
  return signatureHeader.split(" ").some((candidate) => {
    const buf = Buffer.from(candidate);
    return buf.length === expectedBuf.length && timingSafeEqual(buf, expectedBuf);
  });
}

Wire it up against the raw body. In Express, that’s express.raw({ type: "application/json" }); in a Next.js route handler, await req.text() before JSON.parse.

04

Respond, and know the retry rules

Return any 2xx and you’re done. Anything else — or no response within ten seconds — is a failure, and Talent retries on a backoff schedule for up to eight attempts across roughly 24 hours before the delivery is marked exhausted. An endpoint that fails twenty times in a row is disabled automatically, and you get an webhook_endpoint.disabled event.

Send yourself a test delivery any time, and inspect every attempt — request, response, and signature — in the dashboard delivery log.

Shell
curl -X POST "$TALENT_API/webhook-endpoints/$ENDPOINT_ID/test" \
  -H "Authorization: Bearer $TALENT_KEY"

Next

Browse the full event catalog and every endpoint’s payload in the API reference, or manage your endpoints and read delivery logs under Developer → Webhooks.