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}"
      }
    }
  }'
FieldRequiredDefaultDescription
urlyesDestination endpoint
methodnoPOSTPOST or PUT
headersnoExtra 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:

  • opened
  • alert_attached
  • severity_changed
  • acknowledged
  • auto_resolved
  • closed
  • reopened
  • note_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_dedupe and not re-sent. This protects against runner retry bursts.

Disabling

Set incidentSubscribed: false to fall back to the legacy per-alert webhook dispatch.