Webhooks and chat alerts

Get a message when Microsoft posts a new incident, changes its text or status, or resolves it: in a Microsoft Teams, Slack or Discord channel, or as a signed webhook to your own system. For all of Microsoft, one product family, one service or one Azure region. Free, no account.

Subscribe

Teams: create a workflow with “Post to a channel when a webhook request is received” and paste its URL. Slack: an incoming webhook. Discord: Server settings, Integrations, Webhooks.

How it works

  1. You subscribe a URL to a scope. Nothing is sent until you confirm.
  2. Within a minute a verification message arrives. In a chat channel it has a link: open it and press Confirm. A plain webhook gets a signed subscription.verification event: POST {"token": data.token} to https://microsoftradar.com/api/v1/subscriptions/{id}/confirm, or open data.confirm_url.
  3. From then on, every event in your scope is delivered, signed. Every message has an unsubscribe link.

Events

All are sent by default; pass "events": [...] to choose (any of incident.created, incident.updated, incident.resolved, incident.unlisted, status.changed).

API

curl -s https://microsoftradar.com/api/v1/subscriptions -H 'Content-Type: application/json' \
  -d '{"url": "https://example.org/hooks/microsoftradar", "service": "exchange-online"}'

The answer has the subscription id, the signing secret and a manage_token. Both are shown only once. Instead of service you can send family, region, or scope: {"type": "all"}, and type (webhook, slack, teams, discord) when we cannot tell from the URL.

Send Content-Type: application/json with every POST and DELETE, also when there is no body: requests without it are refused as cross-site form posts.

curl -s -X DELETE https://microsoftradar.com/api/v1/subscriptions/sub_…   -H 'Authorization: Bearer mgt_…' -H 'Content-Type: application/json'

Payload

{
  "id": "evt_3f1c…",
  "type": "incident.updated",
  "created_at": "2026-11-13T12:59:17.000Z",
  "subscription": {
    "id": "sub_…",
    "scope": {
      "type": "service",
      "id": "exchange-online"
    }
  },
  "data": {
    "incident": {
      "id": "m365-eb84974213",
      "title": "Users may be unable to connect to multiple Microsoft 365 services",
      "status": "investigating",
      "impact": "minor",
      "official_status": "Service degradation",
      "official_ids": [],
      "services": [
        {
          "id": "exchange-online",
          "name": "Exchange Online"
        }
      ],
      "url": "https://microsoftradar.com/incidents/…/"
    },
    "version": {
      "number": 2,
      "captured_at": "2026-11-13T12:59:17.000Z",
      "changed_words": {
        "changes": [
          {
            "removed": "…",
            "added": "…"
          }
        ],
        "added_words": 92,
        "removed_words": 76
      }
    },
    "summary": "Text edited: 92 words added, 76 removed. …",
    "quote": "Scope of impact: This issue may affect users attempting to access the affected Microsoft 365 services."
  },
  "links": {
    "unsubscribe": "https://microsoftradar.com/developers/webhooks/unsubscribe/?s=…&t=…"
  }
}

Chat channels get the same content as a formatted message (Slack blocks, a Teams Adaptive Card, a Discord embed), with no mentions. Like everything on MicrosoftRadar, messages quote Microsoft briefly (at most 25 words) and link to the source.

Signatures

Every delivery follows Standard Webhooks: headers webhook-id (the same on retries; use it to drop duplicates), webhook-timestamp and webhook-signature, an HMAC-SHA256 with your secret. The Standard Webhooks libraries verify it:

import { Webhook } from 'standardwebhooks';
// secret: the "whsec_…" value you got when you subscribed
const wh = new Webhook(secret.replace('whsec_', ''));
const event = wh.verify(rawBody, {
  'webhook-id': req.headers['webhook-id'],
  'webhook-timestamp': req.headers['webhook-timestamp'],
  'webhook-signature': req.headers['webhook-signature'],
}); // throws when the signature or the time is wrong

Or by hand:

const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const signed = `${id}.${timestamp}.${rawBody}`;
const expected = 'v1,' + crypto.createHmac('sha256', key).update(signed).digest('base64');
// compare with each space-separated entry of webhook-signature, and reject timestamps older than 5 minutes

Delivery, retries and switching off

Privacy

We store the URL, the scope and the secrets that protect them. No email address, no name, no IP address. Chat webhook URLs are shown masked. HTTPS only, public host names only. Email alerts are not offered.

Last updated