Prometheus alerts to Microsoft Teams after the connector shutdown
Tested with Alertmanager 0.28.1 and Prometheus 3.5.
Teams' old incoming webhooks (Office 365 connectors) stopped working on 22 May 2026. Create a Workflows webhook in the channel instead, and send to it with Alertmanager's msteamsv2_configs receiver, which needs Alertmanager 0.28 or later.
If your alert channel went quiet in May, this is probably why. The replacement takes about ten minutes, and Alertmanager now speaks the new format itself, so you don't need a bridge in between.
What changed
- Office 365 connectors are gone. After two years of extensions, Microsoft switched them off between 18 and 22 May 2026. Anything posting to a
webhook.office.comoroutlook.office.comaddress stopped arriving. - Workflows replaces them. A Workflows webhook is a small Power Automate flow that takes a JSON post and puts it in a channel as an Adaptive Card, the format Alertmanager's
msteamsv2receiver sends. - Early Workflows addresses changed too. Power Automate moved its trigger URLs from
logic.azure.comtoenvironment.api.powerplatform.com, and the old ones stopped working on 30 November 2025. A workflow set up before then may still be there with an address that no longer works.
To see where you stand, look for any of the old addresses in your Alertmanager configuration:
grep -rnE 'webhook\.office\.com|outlook\.office\.com|logic\.azure\.com' /etc/alertmanager/
Any match is an address that no longer delivers.
1. Create the webhook in Teams
- In the channel, open the ⋯ menu and choose Workflows.
- Pick Send webhook alerts to a channel. In Power Automate the same template is called "Post to a channel when a webhook request is received".
- Name it after what posts there (for example "Prometheus alerts"), check the team and channel, and finish. Copy the URL it shows.
Treat that URL like a password: the signature in it is the only thing stopping anyone else posting to your channel. Private channels work too, since April 2026.
2. Test it before you touch Alertmanager
Post a minimal card with curl. If this fails, the problem is the workflow, not Alertmanager:
TEAMS_URL='paste the workflow URL here'
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Content-Type: application/json' \
-d '{"type":"message","attachments":[{"contentType":"application/vnd.microsoft.card.adaptive","content":{"type":"AdaptiveCard","version":"1.4","body":[{"type":"TextBlock","text":"Test from curl","wrap":true}]}}]}' \
"$TEAMS_URL"
202 means the workflow accepted it, and the card should appear within a few seconds. If you get 202 and no card, the workflow ran and its posting step failed: open the workflow's run history and read the failed step.
3. Point Alertmanager at it
Check your version first with alertmanager --version. The msteamsv2 receiver arrived in 0.28.0, and Linux distribution packages are often older; the official release or container image is current.
Save the URL in its own file, readable only by the user Alertmanager runs as. Then the configuration itself can live in git without the secret:
route:
receiver: teams
group_by: ['alertname', 'site']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receivers:
- name: teams
msteamsv2_configs:
- webhook_url_file: /etc/alertmanager/teams-url
send_resolved: true
Check the file, reload, and send a test alert through the real path:
amtool check-config /etc/alertmanager/alertmanager.yml curl -X POST http://localhost:9093/-/reload amtool alert add TeamsTest severity=warning --annotation='summary="Test alert, please ignore"' --alertmanager.url=http://localhost:9093
The card arrives after group_wait (30 seconds here), and a "resolved" card follows about five minutes later, when the test alert times out.
4. Make the card readable
The default card is generic. A short title with the alert name and count, and one line per alert, reads better on a phone:
route:
receiver: teams
receivers:
- name: teams
msteamsv2_configs:
- webhook_url_file: /etc/alertmanager/teams-url
title: '{{ if eq .Status "firing" }}Firing{{ else }}Resolved{{ end }}: {{ .CommonLabels.alertname }} ({{ len .Alerts }})'
text: '{{ range .Alerts }}{{ with .Labels.instance }}**{{ . }}**: {{ end }}{{ .Annotations.summary }}{{ "\n\n" }}{{ end }}'
Keep it short. When fifty servers fire at once, a card that lists every label of every alert gets long enough to be useless, and can get too big for Teams to post at all. Grouping alerts well matters more here than templates: see One outage, twenty alerts.
When it doesn't work
| What you see | Why | Fix |
|---|---|---|
Alertmanager won't load a config with msteamsv2_configs | Alertmanager is older than 0.28 | Upgrade |
Nothing arrives, and Alertmanager logs errors for msteamsv2 | An old or mistyped address | Create the workflow again and copy the new URL |
| curl gets 202 but no card appears | The workflow ran and its posting step failed | Read the failed run in the workflow's run history |
| Cards end with a colleague's name: "… used a Workflow template to send this card" | Workflows made from the template show who created them | In Power Automate, save a copy of the workflow, switch Alertmanager to the copy's URL, and delete the original |
| Alerts stop after someone leaves | The workflow belongs to their account | Add a co-owner, or create workflows from a shared service account |
Alert when Teams delivery breaks
If Teams is the only place alerts go, the alert that says "Teams delivery is broken" goes to Teams. Alertmanager counts its own failed deliveries, so watch that counter and send that one alert somewhere else. This needs Prometheus to scrape Alertmanager's own /metrics:
- alert: AlertmanagerNotificationsFailing
expr: sum by (integration) (rate(alertmanager_notifications_failed_total[10m])) > 0
for: 15m
labels:
severity: critical
annotations:
summary: "Alertmanager can't deliver {{ $labels.integration }} notifications"
Then give it its own route to email (or SMS), so it never depends on the thing that broke:
global:
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alertmanager@example.com'
route:
receiver: teams
group_by: ['alertname', 'site']
routes:
- matchers: ['alertname="AlertmanagerNotificationsFailing"']
receiver: email-fallback
receivers:
- name: teams
msteamsv2_configs:
- webhook_url_file: /etc/alertmanager/teams-url
- name: email-fallback
email_configs:
- to: 'oncall@example.com'
For the case where Alertmanager itself is down, nothing inside it can tell you. That needs a heartbeat from outside: see the watchdog in Alert rules that won't burn you out.
One card per incident instead
Alertmanager groups alerts by their labels, so a database failure that takes down three app servers still arrives as several cards about the app servers. Whyfire posts to Teams through the same kind of workflow, but one card per incident: alarms that share a cause arrive together, with the server where it started at the top. The live demo shows one.