Person webhooks
Receive a candidate's own notification events as signed HTTPS POSTs.
matchwire can push a candidate's own notification events to HTTPS endpoints that candidate registers — the person-axis mirror of the job-scoped Webhooks page (MW-1437). This page is for implementers of a candidate's own agent. An endpoint belongs to one person: it receives that person's events only, a URL can be registered once per person (another person may register the same URL), and the endpoint dies with the person's account.
Who may register an endpoint
A person's endpoint sends the person's own activity trail to an external
URL, so the permission to manage endpoints is the one the person granted
when connecting the agent: the notification:write scope on the OAuth
consent screen. A connection without it is refused with
insufficient_scope and nothing is written; there is no further approval
step, and the person withdraws the permission by revoking the connection in
the matchwire app.
Managing endpoints
Six MCP tools (scope notification, candidate realm):
create_person_webhook_endpoint, list_person_webhook_endpoints,
update_person_webhook_endpoint, rotate_person_webhook_endpoint_secret,
delete_person_webhook_endpoint — and the attempt log via
list_person_webhook_deliveries. The signing secret (mw_whsec_*) is
shown once, in the create/rotate response, and can never be read back.
Endpoints must be public HTTPS URLs — plain http, private/loopback
addresses, private-use hostnames, and URLs with embedded credentials are
refused at registration and re-checked at send time.
The payload differs from the job axis
The person-axis event payload is an independent contract,
schemaVersion 1.0.0 — not the job-axis payload:
{
"schemaVersion": "1.0.0",
"deliveryId": "6f8b…",
"kind": "interview_schedule_updated",
"subject": { "kind": "candidacy", "ref": "9d2c…" },
"recipientToken": "b41a…",
"occurredAt": "2026-07-11T02:03:04.000Z"
}- There is no
jobIdfield. A person's own event stream has no job axis — endpoints belong to the person, not to a job — so the payload deliberately carries none. recipientTokenis always the endpoint owner's own opaque token: only the person's own events ever reach their endpoints.- The payload is thin — identifiers only, never content. Fetch current
state through the candidate agent's authenticated MCP reads (a
selection record via
get_selection, a conversation viaget_conversation, a job viaget_published_job_posting), so content always renders behind matchwire's disclosure gate and erasure applies retroactively.
Verifying signatures
Signing is the same module and procedure as the job axis — follow
Webhooks § Verifying signatures
verbatim: parse Matchwire-Signature (t=<unix seconds>,v1=<hex HMAC>),
compute HMAC-SHA256(secret, "<t>." + rawBody) with your mw_whsec_*
secret, compare constant-time, and reject timestamps outside your replay
window (5 minutes recommended). Matchwire-Webhook-Id carries the
person-axis attempt id, and Matchwire-Event-Kind the event kind.
Delivery contract
- At-least-once, unordered. Deduplicate on
deliveryId; order byoccurredAtyourself if you need it. - Respond fast with a 2xx; do heavy work after acknowledging.
- Retries: a
5xx,429,408, or network/timeout failure is retried with exponential backoff up to 10 attempts; any other4xxis permanent and not retried. Each attempt times out after 10 seconds. - Auto-disable: after 10 consecutive permanent failures the
endpoint is disabled automatically and the person receives a
webhook_endpoint_disabledin-app alert (in-app only — a dead webhook cannot carry its own outage notice). Fix the receiver, then re-enable withupdate_person_webhook_endpoint { active: true }. Events published while disabled are not delivered retroactively — catch up withlist_notification_deliveries.
Choosing events
create_person_webhook_endpoint accepts an optional kinds filter — a
non-empty subset of the candidate-receivable notification kinds
(currently 24 kinds). Omit it (or clear it with
update_person_webhook_endpoint { kinds: null }) to receive all
candidate-receivable kinds, including ones added in future releases —
parse tolerantly and route on what you know. A kind no candidate can
receive is refused at registration. Note that webhook_endpoint_disabled
itself, while in the subscribable set, is delivered in-app only and never
arrives over a person webhook.