Self-hosted platform for standing up and running software factories on AWS: complete DevSecOps environments with secure delivery pipelines, supply-chain controls, compliance evidence and multi-tenant governance — in minutes, not months.
Point your existing incident tooling at the platform once, and two DORA metrics fill themselves in.
reporting:ingest scope forces role=member with no features or permissions, so even an admin’s webhook token can only file incidents.(org, incidentId). The normal flow is two POSTs — open, then resolve — and retries or at-least-once deliveries are safe.environment must match the deploy stage. This is the single most common wiring mistake.The incident webhook turns PagerDuty, Datadog, Opsgenie, in-cluster Alertmanager — or any system that can POST JSON — into an automated source of Change Failure Rate and Mean Time To Restore.
| Metric | What an incident supplies |
|---|---|
| Change Failure Rate (CFR) | A production incident correlated to a deploy makes that deploy a post-deploy failure. |
| Mean Time To Restore (MTTR) | A resolved incident gives the real recovery time (resolved_at − opened_at). |
The manual post-deploy outcomes path still works and is deduped against incidents, so you can stop clicking Mark failed / Mark restored without losing what you already marked.
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 are stored but never shown.
/api/reports/incidents with a reporting:ingest bearer token, on incident open.(org, incidentId). A second POST with the same id updates it in place rather than creating a duplicate.incidentId, now carrying resolvedAt.environment whose completed_at ≤ openedAt, within the correlation window.The webhook token is an access key scoped to reporting:ingest — org-bound and least-privilege.
Admin UI (recommended) — Settings → Incident Reporting → Webhook token → Generate webhook token.
It asks you to re-confirm your identity, either with your password or a fresh sign-in with your provider (step-up), and shows the key once. Copy it immediately — only its hash is stored.
To rotate: generate a new one and revoke the old key on the Security → Access keys settings page. The old one stops working within five minutes.
Under the hood this is POST /api/user/keys with { scope: "reporting:ingest" }.
CLI — for the in-AWS-account event forwarder credential, which is a service-account key stored in Secrets Manager with daily rotation:
pipeline-manager infra store-token --scope reporting:ingest
See Onboarding → store the service-account keys.
The window defaults to DORA_INCIDENT_WINDOW_HOURS (24) on the reporting service, and an org admin can override it per-org (1–720 hours) in the Admin UI or via the endpoint — see Per-org correlation window.
Configure a webhook or notification integration that fires on incident open and resolve. See Point your tool here for per-vendor walkthroughs.
Set an environment that matches the environment you declared on the deploy stage. This is what correlation joins on.
Use Send test incident in the Admin UI, or POST /api/reports/incidents/test. It is a non-persisting dry-run, so it proves the wiring without writing anything.
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>
See Configuration step 1 above for both the Admin UI and CLI paths.
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 with { "data": { "incidentId": "PD-4821", "ok": true } }.
| Failure | Response |
|---|---|
| Validation failure | 400 VALIDATION_ERROR |
Token without the reporting:ingest scope |
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 other 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.An org admin can override the window 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, and 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 from 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 and idempotent (org, incidentId) upsert as the generic route.
Alerts missing an environment label, a stable fingerprint, or a valid startsAt are skipped, and the response reports { received, ingested, skipped }. Set an environment label on your alerting rules that matches the environment you declared on the deploy stage.
Every walkthrough below sets Authorization: Bearer <token> and Content-Type: application/json, targeting POST /api/reports/incidents and mapping the tool’s fields to the contract.
Use the native adapter — point a receiver’s webhook_configs.url at the adapter path. No body mapping is needed beyond the environment and 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.incident.id → incidentId, incident.created_at → openedAt, incident.resolved_at → resolvedAt (omit while open), incident.priority / urgency → severity, and a fixed or 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 and 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, and are org-admin surfaces.
environment would correlate to a recent successful deploy under the org’s window. A wiring and 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 token