Self-service AWS CodePipeline platform — developers ship compliant CI/CD pipelines in minutes via dashboard, CLI, CDK, or AI prompt, while platform teams enforce policy-as-code guardrails, governance, and per-team isolation.
The incident webhook turns your existing incident tooling (PagerDuty, Datadog, Opsgenie, in-cluster Alertmanager, or any system that can POST JSON) into an automated source of two DORA metrics:
resolved_at − opened_at), rather than a manually-marked one.Point your incident tool at POST /api/reports/incidents once, and DORA fills in
CFR + MTTR automatically — no more clicking Mark failed / Mark restored by hand
(the manual post-deploy outcomes path still
works and is deduped against incidents).
Incident data only surfaces through DORA, which is an
advanced_reportingfeature (Enterprise, or the Advanced Reporting add-on). Ingesting incidents without the entitlement is harmless — they’re stored but never shown.
The endpoint is a machine endpoint, authorized by the reporting:ingest
token scope — the same org-scoped credential the event forwarder holds. The
org is taken from the token identity, never from the request body, so a token
can only file incidents for its own organization.
Send the token as a bearer credential:
Authorization: Bearer <reporting:ingest-scoped token>
The webhook token is a Personal Access Token scoped to reporting:ingest —
org-bound and least-privilege (the scope forces role=member with no
features/permissions, so even an admin’s webhook token can only file incidents).
Two ways to mint one:
POST /api/user/pats with { scope: "reporting:ingest" }.)pipeline-manager infra store-token --scope
reporting:ingest. See Onboarding → store the service token.POST /api/reports/incidents
Content-Type: application/json
Authorization: Bearer <token>
| Field | Type | Required | Notes |
|---|---|---|---|
incidentId |
string (≤255) | yes | Your incident tool’s stable id. Unique per org — the idempotency key. |
environment |
string (≤255) | yes | The affected deploy environment (e.g. production). Must match the environment you declared on the deploy stage. |
openedAt |
ISO 8601 (offset) | yes | When the incident opened. Used for deploy correlation. |
resolvedAt |
ISO 8601 (offset) | no | When it resolved. Omit for an open incident; send a follow-up POST to set it. |
severity |
string (≤50) | yes | Free-form (critical, P1, warning, …). |
Example:
{
"incidentId": "PD-4821",
"environment": "production",
"openedAt": "2026-08-20T14:05:00Z",
"resolvedAt": "2026-08-20T14:52:00Z",
"severity": "critical"
}
Response: 200 { "data": { "incidentId": "PD-4821", "ok": true } }. Validation
failures return 400 VALIDATION_ERROR; a token without the reporting:ingest
scope returns 403.
Incidents are keyed on (org, incidentId). Posting the same incidentId
again is an upsert, not a duplicate — the typical flow is two POSTs:
openedAt set, resolvedAt omitted.incidentId with resolvedAt now populated.The resolve POST updates resolvedAt (and any changed fields) in place. Retries
and at-least-once webhook deliveries are therefore safe.
Each incident is attributed to the most recent successful deploy to its
environment whose completed_at ≤ openedAt, within DORA_INCIDENT_WINDOW_HOURS
(default 24, configurable on the reporting service). That deploy becomes a
post-deploy failure, and — if the incident resolves — supplies the MTTR gap.
failed outcome, it counts as one post-deploy failure, and the incident takes precedence for MTTR.The window defaults to DORA_INCIDENT_WINDOW_HOURS (24) on the reporting service,
but an org admin can override it per-org (1–720 hours) — in the Admin
UI or via the endpoint:
GET /api/reports/settings/incidents # read { incidentWindowHours, defaultWindowHours,
# eventRetentionDays, doraRetentionDays,
# defaultEventRetentionDays, defaultDoraRetentionDays }
PUT /api/reports/settings/incidents # any subset of { "incidentWindowHours": 12,
# "eventRetentionDays": 45, "doraRetentionDays": 200 }
Both require reports:read + advanced_reporting; the PUT additionally requires
the org-admin org:settings permission. The PUT is a partial upsert — send
any subset; omitted fields are left unchanged. When set, the correlation-window
override is used everywhere the correlation runs (DORA CFR/MTTR, the incidents
list, and the test dry-run); when unset, the env default applies. The same
endpoint carries the two retention overrides (eventRetentionDays /
doraRetentionDays, 1–730 days) — see DORA Metrics → Retention.
In-cluster Prometheus Alertmanager posts a batched payload ({status,
alerts:[…]}) — a different shape than the generic contract. Point a
webhook_config
receiver at the native adapter instead, and it reshapes the batch into one
incident per alert:
POST /api/reports/incidents/alertmanager
Authorization: Bearer <reporting:ingest token>
Mapping (per alert):
| Incident field | From |
|---|---|
incidentId |
alert fingerprint (falls back to the payload groupKey) |
environment |
the environment label (override the label name with ?environmentLabel=<label>) |
severity |
the severity label (defaults to unknown) |
openedAt |
startsAt |
resolvedAt |
endsAt, only when the alert status is resolved (Alertmanager’s “no end” zero value is ignored) |
Same reporting:ingest auth + idempotent (org, incidentId) upsert as the generic
route. Alerts missing an environment label, a stable fingerprint, or a valid
startsAt are skipped (the response reports { received, ingested, skipped }).
Set an environment label on your alerting rules that matches the environment
you declared on the deploy stage. No external relay is needed.
Configure a webhook / notification integration that fires on incident open and
resolve, targeting POST /api/reports/incidents with the reporting:ingest
bearer token and mapping your tool’s fields to the contract. The
walkthroughs below all set Authorization: Bearer <token> and
Content-Type: application/json.
Use the native adapter — point a receiver’s
webhook_configs.url at the adapter path; no body mapping is needed beyond the
environment/severity labels, and firing/resolved is taken from Alertmanager’s
own status.
<PLATFORM_BASE_URL>/api/reports/incidents; add a Custom Header Authorization: Bearer <token>.incident.triggered and incident.resolved events.incident.id → incidentId, incident.created_at → openedAt, incident.resolved_at → resolvedAt (omit while open), incident.priority/urgency → severity, and a fixed/service-derived environment.<PLATFORM_BASE_URL>/api/reports/incidents and add the Authorization: Bearer <token> header.$ALERT_ID → incidentId, $DATE/$LAST_UPDATED → openedAt/resolvedAt, and a literal environment (or a tag template).@webhook-<name> to the message, and send resolvedAt only when $ALERT_TRANSITION is a recovery. Tag the monitor with the environment.<PLATFORM_BASE_URL>/api/reports/incidents; add the Authorization: Bearer <token> header; enable Add Alert Description to Payload as needed.incidentId, timestamps → openedAt/resolvedAt, priority → severity, plus an environment.Any tool that can POST JSON works — map its stable alert id, open/resolve timestamps, environment, and severity to the generic contract and send the bearer token. Use the Send test incident button to verify the wiring before relying on it.
Settings → Incident Reporting (org-admin; gated on advanced_reporting) is the
self-serve setup surface. It shows:
reporting:ingest token (shown once);environment mapping;POST /api/reports/incidents/test # { "environment"?: "production" }
GET /api/reports/incidents?limit=&offset= # recent incidents + correlation, paginated
Both require reports:read + advanced_reporting (org-admin surfaces).
environment would correlate to a recent successful deploy
under the org’s window — a wiring/config check that does not write an incident
or affect metrics. Returns { environment, openedAt, windowHours, correlated,
executionId, deployCompletedAt }.resolved state and
its correlated deploy (correlatedExecutionId / deployCompletedAt, or null).reporting:ingest service tokenreporting:ingest scope