Azure is the easiest of the four clouds to wire in, for one reason that has nothing to do with us: Microsoft standardised the alert payload. Metric alerts, log alerts, activity-log alerts and Resource Health all arrive in the same envelope, with the parts that differ quarantined inside one sub-object.

That single decision removes most of the per-signal special-casing the other clouds need.

Turn on the Common Alert Schema

This is the one configuration step that matters, and it is a checkbox on the action group. Without it you get the older per-signal payloads, which are four different shapes, and the connector reads essentials — which only the common schema provides.

graph LR M["Metric alert"] --> AG["Action group
Common Alert Schema: on"] L["Log alert (KQL)"] --> AG A["Activity log alert"] --> AG RH["Resource Health"] --> AG AG -->|"webhook"| W["/api/webhooks/azure"] W --> E["reads essentials only"] E --> N["One normalized incident"] AG -.->|"schema off"| X["four different shapes
— not supported"] style E fill:#e8f8f4,stroke:#22c1a4 style X fill:#fde8e8,stroke:#e57373

The connector deliberately reads only essentials, never the signal-specific alertContext. That means a signal type we have never tested against still produces a usable incident — it just carries less detail. Reading less makes the connector more robust, not less.

The resource ID is doing a lot of work

Azure identifies everything by a path, and that path is the richest thing in the payload:

/subscriptions/a1b2…/resourceGroups/prod-rg/providers/Microsoft.Web/sites/orders-api

The normalizer splits it into labels — subscription, resource_group, provider, resource — and uses the resource name as the service. Those become the things your match rules bind to, which means a rule like resource_group = prod-rg, provider = Microsoft.Web/sites routes every app-service alert in production to one runbook.

Azure severityBecomesNote
Sev0 / Sev1criticalSev0 is reserved for "service is down" in most estates.
Sev2highThe most commonly used level, and worth routing carefully.
Sev3warning
Sev4infoFrequently noise. Consider whether it should reach an incident tool at all.

monitorCondition carries Fired or Resolved, which is how the close is detected. Worth knowing: Azure's resolve payload drops alertTargetIDs, so the resolved event has no resource, no service and far fewer labels than the one that opened the alert. Matching on a label-derived fingerprint alone would lose every close. The alertId is matched first, which is why it does not.

KQL is the workhorse

Three step types, and the second is the one you will use constantly.

StepWhat it does
azure.metricsA metric on one resource over a window, by aggregation. Takes the resource ID straight from the incident label.
azure.logsA KQL query against Log Analytics. This is where the evidence lives on Azure, and KQL is expressive enough that one step often replaces three.
cloud.healthResource Health events on the subscription — is it your service or the platform?

Because KQL can join, aggregate and compare in a single query, an Azure runbook is usually shorter than its AWS or Kubernetes equivalent. One query can answer "how many exceptions, of what type, on which instances, compared with the same window yesterday" — which on other platforms takes three steps and a reasoning step to compare them.

// step: azure.logs — what changed about the exceptions?
AppExceptions
| where TimeGenerated > ago(30m)
| summarize count() by ProblemId, bin(TimeGenerated, 5m)
| order by TimeGenerated desc

Authentication without the SDK

A note for whoever has to approve this. Azure auth here is plain OAuth 2.0 client credentials against login.microsoftonline.com, or the instance metadata endpoint when you use a managed identity — no azure-identity in the image.

That was a deliberate choice. Carrying the Azure, Google and OCI SDKs together would add more to the container than the whole rest of the application, and all three ultimately do what a hundred lines of HTTP does: exchange something for a bearer token. Tokens are cached until shortly before expiry.

Managed identity is the better answer

If you run InsightResolve in Azure, set AZURE_USE_MANAGED_IDENTITY and skip the client secret entirely. There is then no credential to rotate, leak or store, and the reviewer's question about secret handling answers itself.

Getting it wired

  1. Action group with a webhook receiver pointed at /api/webhooks/azure?token=…, Common Alert Schema enabled.
  2. An app registration with Monitoring Reader on the subscriptions you want read, plus Log Analytics Reader on the workspace — or a managed identity with the same two roles.
  3. Set the workspace GUID as AZURE_LOG_ANALYTICS_WORKSPACE so KQL steps need only the query.
  4. Check Integrations. Azure shows as configured once the tenant is set; credentials are verified on first use, and a failure there names the scope it could not get a token for.

Stated plainly

The Azure connector is built to the documented Common Alert Schema and the published Monitor and Log Analytics APIs, and tested end to end through the product against those shapes — but not against a live Azure tenant. The query steps fail safe. The normalizer is the part to verify with one real alert from your own action group, and it is a five-minute check.

Next in this series

InsightResolve on Google Cloud — and why Error Reporting is the most underrated triage signal on GCP.

Keep reading