Navigation
Getting Started
Guides
Integrations
Integrations
Webhook
Send every incident lifecycle event to your own HTTP endpoint as JSON — schema-versioned for forward compatibility.
Webhook
The webhook integration posts a JSON body for every incident lifecycle event to your own HTTP endpoint. Use this for custom integrations, Opsgenie, Zapier, workflow engines, or anywhere Yorker doesn't ship a purpose-built adapter.
For the underlying model (lifecycle states, event types, scoped hypothesis), see Incidents.
Set up
curl -X POST https://yorkermonitoring.com/api/notification-channels \
-H "Authorization: Bearer $YORKER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "incident-sink",
"channel": {
"type": "webhook",
"url": "https://hooks.example.com/yorker-incidents",
"method": "POST",
"headers": {
"Authorization": "Bearer ${INCOMING_TOKEN}"
}
}
}'| Field | Required | Default | Description |
|---|---|---|---|
url | yes | — | Destination endpoint |
method | no | POST | POST or PUT |
headers | no | — | Extra headers (e.g., auth). A Content-Type header (any casing) is rejected at create/update time. |
Yorker always sends Content-Type: application/json. A user-supplied Content-Type header would break the documented body-parser contract and is refused by the channel schema.
What gets sent
The webhook channel receives every incident event by default:
openedalert_attachedseverity_changedacknowledgedauto_resolvedclosedreopenednote_added
Default payload
{
"schema_version": 1,
"event": {
"eventId": "ievt_001",
"incidentId": "inc_abc",
"teamId": "team_123",
"eventType": "opened",
"actor": { "type": "system", "id": null },
"payload": {
"eventType": "opened",
"observations": {
"sources": ["synthetic_http"],
"syntheticHttp": {
"affectedChecks": [{ "checkId": "chk_api", "checkName": "Checkout API" }],
"symptomWindow": { "startedAt": "2026-04-15T09:58:00.000Z" },
"errorSignature": {
"httpStatusCodes": [503, 504],
"errorCategories": ["upstream_error"],
"locationsAffected": ["loc_us_east_1", "loc_eu_west_1"],
"sampleMessages": ["Bad Gateway", "Gateway Timeout"]
},
"sharedFailingDomains": ["api.stripe.com"]
}
},
"hypothesis": {
"summary": "Stripe API is returning 503/504; checkout is blocked.",
"confidence": 0.75,
"ruledIn": ["shared_failing_domain=api.stripe.com"],
"ruledOut": ["DNS resolution: NXDOMAIN not observed", "TLS: handshake completes"],
"correlationDimensionsMatched": ["shared_failing_domain", "error_pattern"],
"scope": "external_symptoms_only"
},
"title": "Checkout API outage",
"severity": "critical",
"fingerprintHash": "…",
"memberAlertInstanceIds": ["ainst_1", "ainst_2"],
"recurrenceOf": []
},
"occurredAt": "2026-04-15T10:00:00.000Z"
},
"incident": {
"incidentId": "inc_abc",
"title": "Checkout API outage",
"severity": "critical",
"state": "open",
"openedAt": "2026-04-15T10:00:00.000Z",
"triageUrl": "https://yorkermonitoring.com/dashboard/incidents/inc_abc"
}
}schema_version
Every default payload carries schema_version: 1. Gate your consumer on this field and Yorker will not silently break your integration when the default shape evolves — breaking changes bump the version; additive changes don't.
Observations shape
Each source in observations.sources[] (snake_case: synthetic_http, synthetic_browser, synthetic_mcp) has a matching camelCase block (syntheticHttp, syntheticBrowser, syntheticMcp) on the same object. A multi-source incident carries every relevant block. Example consumer:
const obs = event.payload.observations;
if (obs.sources.includes("synthetic_http")) {
// obs.syntheticHttp is present
const statusCodes = obs.syntheticHttp.errorSignature.httpStatusCodes;
}Template overrides
Render your own JSON body with Handlebars. The rendered string must parse as valid JSON — on failure, the default payload is sent instead.
curl -X PUT https://yorkermonitoring.com/api/notification-channels/nch_abc \
-H "Authorization: Bearer $YORKER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"incidentTemplate": {
"channelType": "webhook",
"overrides": {
"opened": {
"body": "{\"type\":\"incident.opened\",\"id\":\"{{incident.incidentId}}\",\"severity\":\"{{incident.severity}}\",\"hypothesis\":\"{{payload.hypothesis.summary}}\",\"triage\":\"{{incident.triageUrl}}\"}"
}
}
}
}'For payloads that want to splat in arbitrary nested structure without mustache-ing every key, use {{jsonBody payload}}.
The render context has the following top-level keys (same as the Slack integration). The event envelope fields mirror serializeIncidentEventForExport: eventId, eventType, incidentId, teamId, actor, occurredAt, payload. In addition, a materialized incident snapshot (title, severity, state, openedAt, triageUrl) is exposed for direct use in templates. There is no top-level event key; use the individual fields or helper as shown below.
{
"body": "{\"type\":\"{{eventType}}\",\"id\":\"{{eventId}}\",\"occurredAt\":\"{{occurredAt}}\",\"actor\":{{{jsonBody actor}}},\"payload\":{{{jsonBody payload}}},\"incident\":{{{jsonBody incident}}} }"
}Notes:
- JSON-producing templates (webhook, Slack, PagerDuty, ServiceNow) compile with Handlebars HTML escaping disabled, so for these channels
{{foo}}and{{{foo}}}produce identical output. Triple-stash is shown here by convention — it makes the intent (raw interpolation into JSON) obvious to readers. Email HTML templates compile with escaping on, where the two forms are NOT equivalent:{{jsonBody payload}}gets HTML-escaped by default (safe) and{{{jsonBody payload}}}is an explicit opt-out of escaping that the template author must choose deliberately. - Handlebars' tokenizer fails on a mustache close that runs directly into a JSON
}—{{{foo}}}}(triple-close + literal) and{{foo}}}(double-close + literal) both raise a parse error. Add a space before the JSON close brace —{{{foo}}} }— to disambiguate. The rendered JSON is otherwise unchanged; if your consumer verifies a canonical-JSON HMAC over the body, re-serialize (e.g.,JSON.stringify(JSON.parse(body))) before hashing so whitespace differences don't break the signature.
A render error or invalid-JSON result falls back to the default payload and logs a warning. Dispatch never fails on a bad template.
Helpers and render context are the same as the Slack integration.
Delivery and retry
- Timeout: Yorker expects a response within the platform HTTP timeout. Slow endpoints risk being recorded as
failed. - Retry: Yorker does not retry failed webhook deliveries on the same event. Use the audit trail (
incident_notification_dispatches) to replay deliveries from your own backfill tooling. - Dedupe: Within a 30-second window, a duplicate event to the same channel is recorded as
skipped_dedupeand not re-sent. This protects against runner retry bursts.
Disabling
Set incidentSubscribed: false to fall back to the legacy per-alert webhook dispatch.