Guide
Outlook Calendar Change Notifications
You want your app to react when an Outlook calendar event changes, so you create a Microsoft Graph subscription — and immediately hit a validation handshake you have 10 seconds to answer, a subscription that expires in under three days, and a clientState you have to check on every delivery. Here's how Graph change notifications work, what trips people up, and how to get the same events as a signed webhook with one command.
Written by Prem Keshari Senior SRE
Command references used in this guide: nylas webhook create, nylas webhook server, nylas webhook verify, and nylas calendar events list.
How do Outlook calendar change notifications work?
Outlook calendar notifications use Microsoft Graph subscriptions. You POST to /subscriptions with the resource (/me/events), a changeType of created,updated,deleted, your notificationUrl, an expirationDateTime, and a secret clientState. When an event changes, Graph POSTs a notification to your URL containing the changed resource's ID — you then fetch the event to see the details.
Before any notifications flow, Graph validates your endpoint. It sends a request with a validationToken query parameter, and your service must echo that token back as text/plain with a 200 inside 10 seconds, or the subscription is rejected. The Graph change notifications overview documents the full handshake and payload shape.
Why the validation handshake and clientState?
Both exist so Graph and your app can trust each other. The validationToken handshake proves you actually control the notificationUrl before Microsoft starts sending real data to it — it stops an attacker from pointing a subscription at someone else's server. The 10-second window is strict, so your endpoint must answer synchronously, before any slow downstream work.
The clientState works the other direction: you set a secret when creating the subscription, Graph echoes it on every notification, and your handler rejects any delivery whose clientState doesn't match. That's your defense against forged POSTs to a public URL. Treat every notification as untrusted until clientState matches and you've re-fetched the resource through an authenticated call.
# Graph requires your endpoint to echo the validationToken first:
# POST /webhooks/outlook-calendar?validationToken=ABC123
# -> respond 200, text/plain, body: ABC123 (within 10 seconds)
# Then each notification carries clientState you must check:
if notification["clientState"] != STORED_CLIENT_STATE:
return 202 # acknowledge but ignore — do not act on itWhat breaks Graph calendar subscriptions?
Expiry is the top failure. A calendar subscription's maximum lifetime is about 4,230 minutes — under three days — so without a renewal job that PATCHes the expirationDateTime ahead of each deadline, notifications stop and the app goes quiet with no error. Microsoft recommends renewing well before expiry to absorb clock skew and transient failures.
Two more catch teams out. Notifications can be missed during outages, so Graph offers lifecycle notifications (reauthorizationRequired, missed) you should also subscribe to and handle. And because a notification only carries the resource ID, every change costs an extra authenticated GET to read the event — rate limits apply to those follow-up calls. Operating all of this is steady work for a calendar-changed signal.
How do you get Outlook calendar webhooks with the CLI?
The nylas webhook create command subscribes to Outlook calendar changes with one call — pass a callback URL and the triggers event.created, event.updated, and event.deleted against a connected Outlook grant. Nylas runs the Graph subscription, the validation handshake, and renewal for you, and delivers each change to your endpoint with event data in the body. Webhook management requires an API key (admin access).
Authenticity is handled cryptographically rather than by an echoed secret: Nylas signs each delivery with an x-nylas-signature HMAC-SHA256 header over the raw body, keyed by your webhook secret. After a notification arrives, nylas calendar events list reads current state if you want full context without a manual Graph GET.
# Subscribe to Outlook calendar changes — one call (needs an API key)
nylas webhook create \
--url https://your-app.example.com/webhooks/outlook-calendar \
--triggers event.created,event.updated,event.deleted \
--description "Outlook calendar change notifications"
# Read current events after a notification arrives
nylas calendar events list --days 30 --jsonHow do you test calendar webhooks locally?
The nylas webhook server command runs a local receiver and, with --tunnel, exposes it over a cloudflared tunnel so real notifications reach your laptop — no public HTTPS endpoint and no 10-second validation handler to stand up first. Pass --secret and the server verifies the HMAC signature on each event before printing it.
For a captured payload, nylas webhook verify checks a body against its signature offline so you can unit-test a handler with a fixture. Native Graph subscriptions are the right tool when you need tenant-wide change tracking or Graph-specific resource data; for most apps reacting to calendar changes, the signed-webhook path removes the handshake, the renewal job, and the lifecycle-notification bookkeeping. See the Google Calendar push guide for the Google equivalent.
# Receive real notifications locally over a tunnel, signatures verified
nylas webhook server --tunnel --secret "$NYLAS_WEBHOOK_SECRET"
# Verify a captured payload offline against its signature
nylas webhook verify \
--payload-file event.json \
--secret "$NYLAS_WEBHOOK_SECRET" \
--signature "$SIG_FROM_HEADER"Next steps
- Google Calendar push notifications — the Google equivalent and its token error
- Verify webhook signatures — HMAC checks and replay protection
- Outlook CLI: manage mail and calendar — work with Outlook from the terminal
- Webhook events reference — every calendar and message trigger
- Full command reference — every flag and subcommand documented