Self-Service CI/CD for AWS

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.

Incident Webhook

Overview

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:

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_reporting feature (Enterprise, or the Advanced Reporting add-on). Ingesting incidents without the entitlement is harmless — they’re stored but never shown.

Authentication

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>

Getting a token (self-serve)

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:

Contract

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.

Idempotency

Incidents are keyed on (org, incidentId). Posting the same incidentId again is an upsert, not a duplicate — the typical flow is two POSTs:

  1. On openopenedAt set, resolvedAt omitted.
  2. On resolve — the same 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.

Correlation window

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.

Per-org correlation window

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.

Alertmanager adapter (native)

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.

Point your tool here

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.

Alertmanager

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.

PagerDuty

  1. Integrations → Generic Webhooks (v3) → New Webhook (or an Events/Webhook v3 subscription).
  2. Webhook URL = <PLATFORM_BASE_URL>/api/reports/incidents; add a Custom Header Authorization: Bearer <token>.
  3. Subscribe to incident.triggered and incident.resolved events.
  4. Use a custom payload template to emit the contract: incident.idincidentId, incident.created_atopenedAt, incident.resolved_atresolvedAt (omit while open), incident.priority/urgencyseverity, and a fixed/service-derived environment.

Datadog

  1. Integrations → Webhooks → New — set URL = <PLATFORM_BASE_URL>/api/reports/incidents and add the Authorization: Bearer <token> header.
  2. Define the Payload with the contract fields using Datadog variables: $ALERT_IDincidentId, $DATE/$LAST_UPDATEDopenedAt/resolvedAt, and a literal environment (or a tag template).
  3. On each monitor that represents production health, add @webhook-<name> to the message, and send resolvedAt only when $ALERT_TRANSITION is a recovery. Tag the monitor with the environment.

Opsgenie

  1. Settings → Integrations → Add → Webhook.
  2. Webhook URL = <PLATFORM_BASE_URL>/api/reports/incidents; add the Authorization: Bearer <token> header; enable Add Alert Description to Payload as needed.
  3. Enable the Alert Created and Alert Closed notifications, and map the alert’s stable id → incidentId, timestamps → openedAt/resolvedAt, priority → severity, plus an environment.

Anything else

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.

Admin UI

Settings → Incident Reporting (org-admin; gated on advanced_reporting) is the self-serve setup surface. It shows:

Test + list endpoints

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).