Polling a calendar API every few minutes to spot new meetings is slow, wasteful, and a fast way to burn through rate limits. The native push systems make it worse: Google wants you to register and renew watch channels, and Microsoft Graph wants per-resource subscriptions you renew every few days. A multi-provider app ends up running two separate systems just to learn that someone moved a meeting.
Nylas webhooks collapse that into one push channel. You subscribe a single HTTPS endpoint to the calendar and event triggers you care about, and the same event.updated handler fires whether the change happened in Google Calendar or Outlook. For the general webhook setup that also covers email and grant events, see Get real-time updates with webhooks.
Subscribe to calendar and event changes
Section titled “Subscribe to calendar and event changes”A calendar webhook is a push subscription: you register an HTTPS URL and a list of trigger_types, and Nylas sends an HTTP POST with a JSON body each time a matching change occurs. The six calendar triggers cover both event lifecycle and calendar lifecycle, and one subscription can carry all of them across every connected provider.
Create the subscription with a single request to POST /v3/webhooks/. Pass your endpoint as webhook_url and the triggers in trigger_types. The notification_email_addresses field is optional and tells Nylas where to email you if the endpoint starts failing. Subscribing to all six triggers covers every calendar and event change.
curl --request POST \ --url 'https://api.us.nylas.com/v3/webhooks/' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data-raw '{ "trigger_types": [ "event.created", "event.updated", "event.deleted", "calendar.created", "calendar.updated", "calendar.deleted" ], "webhook_url": "https://yourapp.com/webhooks/nylas", "description": "Calendar and event sync", "notification_email_addresses": ["[email protected]"] }'import Nylas, { WebhookTriggers } from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>" });
const webhook = await nylas.webhooks.create({ requestBody: { triggerTypes: [ WebhookTriggers.EventCreated, WebhookTriggers.EventUpdated, WebhookTriggers.EventDeleted, WebhookTriggers.CalendarCreated, WebhookTriggers.CalendarUpdated, WebhookTriggers.CalendarDeleted, ], webhookUrl: "https://yourapp.com/webhooks/nylas", description: "Calendar and event sync", },});
console.log("Webhook created:", webhook);from nylas import Clientfrom nylas.models.webhooks import WebhookTriggers
nylas = Client("<NYLAS_API_KEY>")
webhook = nylas.webhooks.create( request_body={ "trigger_types": [ WebhookTriggers.EVENT_CREATED, WebhookTriggers.EVENT_UPDATED, WebhookTriggers.EVENT_DELETED, WebhookTriggers.CALENDAR_CREATED, WebhookTriggers.CALENDAR_UPDATED, WebhookTriggers.CALENDAR_DELETED, ], "webhook_url": "https://yourapp.com/webhooks/nylas", "description": "Calendar and event sync", })
print(webhook)Verify the webhook challenge
Section titled “Verify the webhook challenge”Before Nylas delivers any notification, it confirms you own the endpoint with a one-time handshake. The first time you create a webhook, or set an existing one back to active, the API sends a GET request to your webhook_url with a single challenge query parameter. Your endpoint has 10 seconds to answer.
Your handler must return the exact value of challenge in the body of a 200 OK response, with no quotes, JSON wrapping, or extra characters. Returning anything else fails verification and the webhook stays inactive. The snippet below echoes the parameter straight back.
app.get("/webhooks/nylas", (req, res) => { res.status(200).send(req.query.challenge);});@app.get("/webhooks/nylas")def verify(challenge: str): return Response(content=challenge, media_type="text/plain", status_code=200)A successful handshake generates your webhook_secret, which you use to verify every notification that follows. The challenge handshake details cover the full verification flow.
Handle event notifications
Section titled “Handle event notifications”After verification, Nylas POSTs a notification to your endpoint each time a subscribed trigger fires. The payload identifies the change by type and includes the affected object’s id and grant_id. For an event.updated notification, that’s the event ID and the calendar it belongs to, so your handler knows exactly what changed without re-listing the calendar.
The notification carries identifiers, not always the complete object. The reliable pattern is to read the object ID from the payload, then make one GET request for the full event, since that response always reflects current provider state. Always answer with a 200 OK within 10 seconds, then process asynchronously.
@app.post("/webhooks/nylas")async def handle(request: Request): payload = await request.json() if payload["type"] == "event.updated": data = payload["data"]["object"] event = nylas.events.find( identifier=data["grant_id"], event_id=data["id"], query_params={"calendar_id": data["calendar_id"]}, ) sync_to_database(event) return Response(status_code=200)Things to know about calendar webhooks
Section titled “Things to know about calendar webhooks”Calendar webhooks behave consistently across providers, but a few details affect how you build and debug them. There are six calendar-related triggers in total, and you subscribe to any subset of them on a single endpoint.
The full trigger list:
| Trigger | Fires when |
|---|---|
event.created | A new event is added to a calendar |
event.updated | An existing event changes |
event.deleted | An event is removed |
calendar.created | A new calendar is added |
calendar.updated | A calendar’s metadata changes |
calendar.deleted | A calendar is removed |
Webhooks beat polling on latency. A subscription pushes a notification within seconds of the change, while a polling loop checking every 5 minutes averages 2.5 minutes of lag and wastes one request per cycle whether anything changed or not. Across a few thousand users, that gap is the difference between a live calendar and a stale one.
Calendar uses standard webhooks, not Pub/Sub. Gmail real-time email sync can route through Google Cloud Pub/Sub, but calendar and event triggers always arrive over the standard Nylas webhook channel described here. You don’t need a Pub/Sub topic or any Google Cloud setup to receive event.created.
Verify the signature on every notification. Each delivery carries an X-Nylas-Signature header: a hex-encoded HMAC-SHA256 of the raw request body, signed with your webhook_secret. Recompute the HMAC over the unmodified body and compare before trusting the data. The webhook security guide walks through the check, including the case where the body arrives compressed.
Sync timing differs by provider. Google and Microsoft both deliver calendar changes quickly, but Microsoft occasionally batches updates, so a single edit can produce more than one event.updated for the same event. Treat your handler as idempotent: key on the event id and apply the latest state rather than assuming one notification per change.
Retries are automatic. If your endpoint doesn’t return a 200 OK within 10 seconds, Nylas retries with backoff and marks the endpoint failing, then failed, after repeated misses. Respond fast and do the heavy work in a queue. See Get real-time updates with webhooks for the retry and failure rules.
What’s next
Section titled “What’s next”- Get real-time updates with webhooks for the unified setup across email, calendar, and grant events
- Using webhooks with Nylas for the full setup, verification, and failure handling
- Secure a webhook for signature verification on every notification
- Checking calendar availability to combine real-time sync with free/busy lookups