[object Object]

The Teams notification flow stops working on a Friday afternoon. The token expired, an Azure admin rotated the app secret last month, and the Graph permission for Channel.Send was tightened to a more specific scope two weeks ago. None of these events touched ServiceNow, all of them broke the integration, and there is no monitoring on the Graph side that tells anyone. Microsoft Graph integrations need patterns built specifically for this drift.

Use IntegrationHub Spokes where they exist

For the common surfaces — Teams messages, calendar events, Outlook mail, OneDrive files — the Microsoft Graph spokes are maintained by ServiceNow against Graph version updates. Custom REST against Graph endpoints means owning every breaking change Microsoft ships. Use the spoke unless you have a specific reason; the version drift maintenance cost is real.

App registration per integration

One Azure app registration shared across ten ServiceNow integrations means one secret rotation breaks ten flows simultaneously and one over-scoped permission grant exposes ten flows. Register one app per integration with the minimum scopes required. The Azure admin team will push back on registration sprawl; counter with the blast radius argument.

sn-incident-to-teams      -> ChannelMessage.Send (single channel restriction)
sn-cmdb-from-intune       -> DeviceManagementManagedDevices.Read.All
sn-onboarding-calendar    -> Calendars.ReadWrite (specific user delegation)

Token refresh as a first-class concern

Bearer tokens from Graph last 60-90 minutes by default. The spoke handles refresh automatically; custom integrations often cache for the full lifetime and fail at the boundary. If you must roll your own, refresh proactively at 80% of token lifetime, not on 401 — reactive refresh causes a request storm under load.

// Token cache with proactive refresh
var cached = gs.cache.get('msgraph_token_'+appId);
if (!cached || cached.expires_at - Date.now() < 0.2 * cached.lifetime) {
  cached = msGraphAuth.refresh(appId);
  gs.cache.put('msgraph_token_'+appId, cached);
}

Throttling is constant, not exceptional

Graph throttles aggressively per app, per tenant, and per resource. A 429 with Retry-After is normal traffic, not an error. Implement exponential backoff with jitter on every Graph call and respect Retry-After exactly. Bulk operations like user enumeration should use $batch (max 20 per batch) and pace explicitly to under 4 batches per second per app.

Webhook subscriptions need renewal

Subscribing to Graph change notifications (incoming Teams messages, mailbox changes) requires renewing the subscription before expiration — typically 3 days for messages, longer for other resources. Build a scheduled job that lists active subscriptions, finds ones expiring within 24 hours, and renews. A subscription that lapses is silently dead.

Mock Graph for sub-prod

Graph integration testing in sub-prod against the real Graph hits production data and real-user mailboxes — both bad. Either use a dedicated test tenant (the right answer for serious integrations) or stub the Graph endpoints with a Scripted REST API that returns canned responses. Test against the stub for everything except the final integration test.

Common failure modes

Permission grant changes from delegated to application-only without updating the integration — the calls succeed but with the wrong identity context. Tenant-wide admin consent revoked silently — monitor the consent state via Graph’s directory audit. Reply URLs misconfigured after Azure UI changes — pin reply URLs and audit them quarterly.

What changed in 2026

Microsoft has begun rolling out per-tenant rate-limit dashboards in Entra; capture your tenant’s published limits and monitor against them, not the global defaults. Graph v1.0 vs beta endpoint divergence has narrowed but the Notes API and some Teams admin endpoints remain beta-only — beta endpoints can change without warning, so plan a migration off beta within 90 days of any GA equivalent.

What to do this week: list every Azure app registration used by ServiceNow, identify the secret expiration date, and put rotation reminders on the calendar 30 days before each.

[object Object]
Share