SLA breach escalation: Intercom → Slack → PagerDuty
SLA breach pings Slack; if it sits unread past the wait window, PagerDuty pages someone.
Intercom's own "When SLA breaches" trigger (Expert plan, beta, via Intercom Support) already pings Slack when an SLA is missed — this doesn't compete with that. The gap is after: nothing escalates to PagerDuty if the ping goes unread. Intercom's sla_status only updates once someone replies, so it misses a conversation still open and overdue — this instead tracks waiting_since against each rule's target minutes, alerts Slack when that clock runs out, waits a window, then pages PagerDuty if still unhandled, auto-resolved once answered.
How it flows
- 01
Poller scans open conversations on an interval
Search /conversations/search for open = true, scoped by team_assignee_id. Neither sla_applied.sla_status nor a numeric SLA target is searchable, so this scoped list is only the candidate set — the real check happens per conversation next.
- 02
Each result checked against its own SLA clock
GET /conversations/{id} on every candidate; compare waiting_since to the target minutes recorded for that team or priority in setup. A conversation counts as breached the moment that local clock runs out while it is still waiting on a reply — not when Intercom's own sla_status changes, since that field only updates retroactively on the next admin reply.
- 03
Slack ping fires on the assignee
DM (or channel post) names the SLA rule and links the conversation. The alert timestamp and conversation id are recorded — that record is the wait window's clock.
- 04
Wait window elapses
Configurable, e.g. 15–30 minutes. Nothing fires during this window on purpose — it is the time for a human to actually answer before escalation kicks in.
- 05
Re-check: still open, still no new reply
Compare statistics.last_admin_reply_at / updated_at against the recorded alert timestamp. There is no single Intercom field that means "ignored since this alert" — this comparison is the definition this workflow uses for it.
- 06
Still unhandled → PagerDuty incident
A trigger event fires with the Intercom conversation id as dedup_key, the severity decided in setup, and a summary naming the SLA rule and linking the conversation.
- 07
A reply or a close resolves the same incident
Once the conversation gets an admin reply or is closed, a resolve event fires with the same dedup_key, so the PagerDuty incident does not dangle after the fact.
Set up each app
Work through these in order — later apps usually need a token or an id from an earlier one.
Intercom
Source of SLA state on each conversation
- 01
Confirm the workspace is on Expert plan
SLA rules require Intercom's Expert plan, currently $132/seat/mo billed annually; "Inbox Premium" is not current Intercom naming and does not appear on Intercom's pricing page, so do not look for it. Two native paths already reach Slack before this workflow does anything: any Workflow's entry rules can fire a "Notify a teammate or Slack channel" action off an SLA-tagged conversation, and there is now a dedicated "Before/When SLA breaches" trigger doing the same thing more directly (Settings → Workflows → new Workflow → trigger: Before/When SLA breaches — still beta, unlocked by contacting Intercom Support). Neither is what this workflow replaces; the gap starts the moment that Slack ping goes unread, which is where the rest of this entry picks up. Settings → Inbox → SLAs shows whether an SLA rule already exists; if the workspace isn't on Expert, there is nothing here for this workflow to read.
- 02
Create or verify the SLA rule(s)
Settings → Inbox → SLAs → define the rule(s) that set sla_applied.sla_name on matching conversations (for example "VIP customer <5m"). This workflow only reads whatever rules already exist here; it does not create them.
- 03
Record each rule's target minutes — this workflow tracks its own clock
Intercom exposes sla_applied.sla_name on a conversation (a label like "VIP customer <5m") but no queryable field for the rule's numeric target, and sla_applied.sla_status is set only retroactively — the next time a teammate replies, not when the target actually passes. That makes it useless for catching a conversation that is still open and overdue. Instead, write down each rule's response-time target in minutes from the SLA screen above, keyed by team or priority, as a config value this workflow reads directly rather than trusting Intercom's own status field for real-time detection. Intercom's own SLA clock is business-hours aware (a target due after your office hours end rolls to the next opening), so if this rule runs against a limited office-hours calendar, only run this workflow's poll during the same hours — otherwise it will fire false-positive alerts overnight or over a weekend that Intercom itself would not count as late yet.
- 04
Mint a token with read (and optionally write) conversations
A Personal Access Token or OAuth app. Select "Read conversations" — that alone is enough to detect a breach. Add "Write conversations" only if the escalation step should also tag or leave a note on the conversation; skip it if Slack and PagerDuty are the only outputs.
- 05
Scope the poll by team or priority
Neither sla_applied.sla_status nor a numeric SLA target is an accepted field on /conversations/search, so there is no single call that returns "every conversation currently overdue." Detection means searching for open conversations, then calling GET /conversations/{id} on each result — this time to read waiting_since and compare it locally against the target minutes recorded above. Scope the search by team_assignee_id (or priority) to the team this SLA rule actually covers; polling every open conversation workspace-wide makes the fan-out grow with total ticket volume instead of with the rule's real scope. Get the team id from GET /teams (List Teams) — Settings → Teams does not surface it directly in the UI.
POST https://api.intercom.io/conversations/search{ "query": { "operator": "AND", "value": [ { "field": "open", "operator": "=", "value": true }, { "field": "team_assignee_id", "operator": "=", "value": "TEAM_ID" } ] } }
Slack
First-line alert to the conversation's assignee
- 01
Add Bot Token Scopes
api.slack.com/apps → your app → OAuth & Permissions → Bot Token Scopes. Add the three scopes below, then reinstall the app for the new scopes to take effect.
Bot Token Scopeschat:write # post the breach alert to a channel or a DM users:read.email # look up a Slack user id from the Intercom admin's email im:write # open a DM with the assignee - 02
Install to workspace and invite the bot
Install to Workspace, then /invite @your-app-name into whatever channel should also see the alert — a DM-only setup does not need this step.
- 03
Map the Intercom assignee to a Slack user id
users.lookupByEmail on the conversation's assigned admin's email — this assumes Intercom and Slack accounts share an email domain, which holds under single SSO and can silently miss otherwise. If the lookup misses, still post the alert to the team channel with the plain Intercom admin name rather than dropping it.
PagerDuty
The hard escalation once the wait window elapses unanswered
- 01
Add an Events API v2 integration
Service Directory → your service → Integrations → Add another integration → Events API V2. Copy what the UI labels the Integration Key; that is the routing_key this workflow posts to. Auth is the key itself — no separate bearer token.
- 02
Decide the severity
This workflow only reaches PagerDuty after a Slack alert already went unanswered past the wait window, so a flat severity: critical is defensible for a single SLA rule. If several SLA rules of different urgency route through the same integration, map each rule's sla_name to its own severity instead of hardcoding critical for all of them.
- 03
Watch the per-key rate ceiling
Events API v2 caps at roughly 120 calls/minute per integration key. If more than one SLA rule or team routes to the same PagerDuty service, split them across per-team integration keys or stagger the fan-out — do not assume one key absorbs every breached conversation from a single poll tick.
- 04
Fire the trigger event once still unhandled
POST to /v2/enqueue with event_action: trigger, dedup_key set to the Intercom conversation id, the severity decided above, and a summary built from the SLA rule name plus the conversation link. Send this only after the re-check step confirms the conversation is still open with no new reply — not the moment the local SLA clock first runs out.
POST https://events.pagerduty.com/v2/enqueue{ "routing_key": "R02A9F4C7E1B84D3F", "event_action": "trigger", "dedup_key": "intercom-conv-48213", "payload": { "summary": "SLA missed (VIP customer <5m) — conversation 48213 still open 22m after Slack alert", "severity": "critical", "source": "intercom-sla-escalation", "class": "sla-breach" }, "links": [{ "href": "https://app.intercom.com/a/inbox/_/conversation/48213", "text": "Open conversation" }] } - 05
Resolve the same incident once handled
Once the conversation gets an admin reply or is closed, POST /v2/enqueue again with event_action: resolve and the same dedup_key, so the PagerDuty incident does not dangle after the fact.
POST https://events.pagerduty.com/v2/enqueue{ "routing_key": "R02A9F4C7E1B84D3F", "dedup_key": "intercom-conv-48213", "event_action": "resolve" }