Incident Alerting Platforms¶
Paging the on-call engineer is arguably the best-fitting category in this whole book: alerting platforms are built to ingest machine-generated events over plain HTTP, they all speak JSON, and — the headline — their APIs are designed idempotent, which upgrades pg_relay's delivery guarantee in a way email and SMS never can. Twelve platforms assessed, eleven fit, and the first adapter has shipped: PagerDuty (p_provider => 'pagerduty' — setup and usage in Resend, Telnyx, and PagerDuty) — the industry's reference ingestion API, and (not coincidentally) the API whose body-side routing_key motivated the body_merge feature that now serves this whole category. The other rows remain assessments — not yet selectable at create_profile(). (Worked wire-level profiles for all eleven: pg_relay's incident alerting chapter.)
The idea that makes this category special: dedup keys¶
pg_relay delivers at least once: a crash between a successful send and the commit re-sends the event. For email, the deterministic Message-ID merely mitigates that; for alerting platforms it disappears entirely, because their ingestion APIs deduplicate by design — PagerDuty's dedup_key, incident.io's deduplication_key, Splunk On-Call's entity_id. Derive that field deterministically from the notification's own primary key (pgrelay-{id}) and a redelivered event updates the alert it already created instead of paging twice. At-least-once becomes exactly-one-alert. Every adapter in this category will derive the dedup field this way — never randomly.
The same key drives the alert's lifecycle from the database side: closing an alert is just another outbound send — event_action: "resolve" (PagerDuty) with the same dedup key — so a second channel can resolve what the first one triggered. What never flows back is acknowledgement state: platforms report ack/resolve through their own outbound webhooks, and pg_relay has no inbound listener — the platform owns the human side.
The assessment¶
| Platform | Fits? | Auth shape | Notes |
|---|---|---|---|
| PagerDuty | ✔ shipped adapter | body_merge (routing_key) |
Events API v2; 202 → dedup_key — use it |
| Jira Service Management | ✔ | Custom header (GenieKey …) |
Opsgenie's API relocated into JSM; per-integration key (not an account token); region-sensitive base URL; alias is the dedup key |
| incident.io | ✔ | Bearer | Clean fit; HTTP alert-source URL carries the source id |
| Better Stack | ✔ | Bearer | Clean fit |
| FireHydrant | ✔ | Bearer | Clean fit (Signals or /v1/incidents) |
| Rootly | ✔ | Bearer | JSON:API body shape; Content-Type: application/vnd.api+json via headers |
| Grafana Cloud IRM | ✔ | URL-keyed | Whole integration URL is the credential (as _env:); OSS OnCall is archived — IRM only |
| Splunk On-Call | ✔ | URL-keyed | entity_id deduplicates; RECOVERY on the same id resolves |
| Squadcast | ✔ | URL-keyed | Must use its Incident Webhook API — its v3 API needs a token-exchange flow, which is out |
| xMatters | ✔ | Basic | Event body depends on the receiving workflow — live-verify especially carefully |
| Runframe | ✔ | URL-keyed or Bearer | Both patterns offered; webhook route is natural for alerts |
| OnPage | ✘ excluded | Login flow + callbacks | Auth is a session-token flow, not a static credential, and its status model requires inbound callbacks — both structural, not recipe gaps |
Classification is uniform across the table — 2xx → sent (id or dedup key as provider ref), 429/5xx/no-response → retry, other 4xx → failed — and unlike SMS's Infobip, nobody here hides failure inside a 200.
PagerDuty and body_merge — the motivating case¶
PagerDuty's Events API v2 authenticates with a routing_key inside the JSON body — no auth header at all. That's precisely what the profile's body_merge overlay exists for: the key lives in a Processor-host environment variable, the profile injects it at send time, and the notifier's message data never contains it. If a producer does supply a routing_key, the profile's value silently overwrites it — the key is profile-owned, by design, so a producer can neither spoof nor redirect the paging destination. The profile carries a placeholder auth block (the transport requires one; PagerDuty ignores it) plus:
The shipped adapter mirrors Slack's shape: p_provider => 'pagerduty', summary and severity from the notification (with p_payload carrying event-native extras — event_action, custom_details, component, and the rest), the dedup key derived from the notification id automatically (explicit for acknowledge/resolve, which must target the trigger's key), and PagerDuty's returned dedup_key recorded as the provider reference. create_profile() enforces the body_merge routing key up front. Full setup: Resend, Telnyx, and PagerDuty.
One honest caveat, inherited deliberately¶
The assessments on this page (and the SMS page) are documentation-verified, not live-verified — endpoints, auth mechanics, and response shapes come from each platform's current published documentation, the same standard pg_relay's book states for its recipes. That includes the shipped PagerDuty adapter itself: unlike Slack, it has not yet been proven with live sends — trigger one real event against a test service before relying on it in production, and treat any profile built ahead of an adapter as a design to be smoke-tested.
Which platform, if you're choosing fresh¶
If you already run one, the table answers whether it fits. If you're choosing: PagerDuty if you want the ecosystem default and the first shipped adapter here; incident.io / Better Stack / FireHydrant if you want the cleanest possible API surface (plain bearer + JSON); JSM Operations if your organisation is already Atlassian-shaped. The platforms whose only route is a URL-keyed ingestion endpoint work fine, but a revocable token beats a secret-bearing URL for credential hygiene — same reasoning as Mattermost's two routes.
Back to the provider overview, or the status table.