Webhooks

Receive matchwire notification events as signed HTTPS POSTs.

matchwire pushes notification events to your registered HTTPS endpoints as signed, thin JSON POSTs (MW-443, ADR-0106). One kind is deliberately excluded: webhook_endpoint_disabled (the auto-disable alert) is delivered in-app only — a dead webhook cannot carry its own outage notice. Register and manage endpoints with the MCP tools (create_webhook_endpoint, list_webhook_endpoints, update_webhook_endpoint, rotate_webhook_endpoint_secret, delete_webhook_endpoint — scope notification); observe deliveries with list_webhook_deliveries.

The full event catalog (every kind, its subject, and when it fires) and an end-to-end subscribe → read → write-back recipe live on ATS integration.

The event payload is thin — re-fetch for detail

A delivery body carries identifiers only, never content:

{
  "schemaVersion": "1.0.0",
  "deliveryId": "6f8b…",
  "kind": "candidacy_stage_changed",
  "subject": { "kind": "candidacy", "ref": "9d2c…" },
  "recipientToken": "b41a…",
  "occurredAt": "2026-07-11T02:03:04.000Z"
}

Fetch the current state through your authenticated MCP/REST credential (for example a candidacy subject via get_pipeline_candidacy, a job via get_job_posting, a thread via get_employer_thread). This is deliberate: content always renders behind matchwire's disclosure gate, so a webhook can never leak what your credential could not read — and erasure or consent withdrawal applies retroactively because nothing is copied out.

Verifying signatures

Every request carries these headers:

HeaderValue
Matchwire-Signaturet=<unix seconds>,v1=<hex HMAC>
Matchwire-Webhook-IdThe delivery attempt id (webhook_deliveries.id)
Matchwire-Event-KindThe event kind, for cheap routing
Content-Typeapplication/json

To verify:

  1. Parse t and v1 from Matchwire-Signature.
  2. Compute HMAC-SHA256(secret, "<t>." + rawBody) with the mw_whsec_* secret returned when you created (or last rotated) the endpoint, and hex-encode it.
  3. Compare with v1 using a constant-time comparison.
  4. Reject requests whose t is outside your replay window — 5 minutes is the recommended tolerance.
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret: string, header: string, rawBody: string): boolean {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header);
  if (match === null) return false;
  const [, t, v1] = match;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min
  const mac = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(mac), Buffer.from(v1 as string));
}

The secret is shown once in the create_webhook_endpoint / rotate_webhook_endpoint_secret response and can never be read back. Rotate immediately if you suspect a leak — the old secret stops signing the moment rotation completes.

Delivery contract

  • At-least-once, unordered. The same event can arrive more than once and events may arrive out of order. Deduplicate on deliveryId and, if you need ordering, sort by occurredAt (tie-break deliveryId) yourself.
  • Respond fast with a 2xx. Anything in 200–299 marks the delivery succeeded. Do heavy work asynchronously after acknowledging.
  • Retries: a 5xx, 429, 408, or network/timeout failure is retried with exponential backoff up to 10 attempts. Any other 4xx is treated as permanent and is not retried. Each attempt times out after 10 seconds.
  • Auto-disable: after 10 consecutive permanent failures, the endpoint is disabled automatically and its creator receives a webhook_endpoint_disabled in-app notification. Fix the receiver, then re-enable with update_webhook_endpoint { active: true } (this also resets the failure counter). Events published while disabled are not delivered retroactively — catch up with list_notification_deliveries.
  • Requirements: endpoints must be public HTTPS URLs — plain http, private/loopback addresses, private-use hostnames (.local, .internal, .lan, home.arpa, a dotless single label, …), and URLs with embedded credentials are refused at registration and re-checked at send time.

Choosing events

create_webhook_endpoint accepts an optional kinds filter (a non-empty subset of the notification kinds). Omit it to receive all kinds, including ones added in future releases — unknown kind values must not break your consumer (parse tolerantly, route on what you know).

On this page