Skip to content

How to sync calendar events to a CRM

Sales teams, recruiters, and account managers live in their calendars. Every meeting with a prospect, candidate, or client is a data point your CRM should capture. But CRM records fall behind because nobody manually logs every meeting, rescheduled call, or cancelled demo. The data drifts, pipeline reports get stale, and managers lose visibility into what’s actually happening.

This tutorial builds a sync pipeline that automatically mirrors calendar events to your CRM whenever something changes. You subscribe to Nylas webhooks for event changes, receive real-time notifications, fetch the full event details, and push structured records to your CRM’s API. One integration handles Google Calendar, Outlook, and Exchange without any provider-specific code.

The pipeline follows a straightforward webhook-driven architecture:

  1. Subscribe to event.created, event.updated, and event.deleted webhook triggers on your Nylas application.
  2. Receive real-time POST notifications from Nylas whenever a calendar event changes on any connected grant.
  3. Fetch the full event details from the Nylas Events API (for created and updated events).
  4. Map Nylas event fields to your CRM’s record schema.
  5. Push the transformed record to your CRM’s API to create, update, or soft-delete the corresponding entry.

This approach works with any CRM that has a REST API: Salesforce, HubSpot, Pipedrive, or a custom internal system. The examples in this tutorial use generic REST endpoints so you can adapt them to your specific CRM.

Make sure you have the following before starting this tutorial:

  • A Nylas account with an active application
  • A valid API key from your Nylas Dashboard
  • At least one connected grant (an authenticated user account) for the provider you want to work with
  • Node.js 18+ or Python 3.8+ installed (depending on which code samples you follow)

New to Nylas? Start with the quickstart guide to set up your app and connect a test account before continuing here.

You also need:

  • A connected grant with calendar read access for at least one user
  • A CRM (or any external system) with a REST API you can push records to
  • A publicly accessible HTTPS endpoint to receive webhook notifications

Nylas blocks requests to ngrok URLs. Use VS Code port forwarding or Hookdeck to expose your local server during development.

Start by creating a webhook subscription that listens for all three event lifecycle triggers. This single subscription covers every grant in your Nylas application, so you don’t need to set up per-user webhooks.

Nylas responds with the webhook ID and a webhook_secret you’ll use to verify incoming notifications. Store that secret securely.

After you create the webhook, Nylas sends a verification GET request with a challenge query parameter to your endpoint. Your server must return the exact challenge value in a 200 OK response. See Using webhooks with Nylas for the full verification flow.

Your endpoint must respond within 10 seconds. If verification fails, Nylas marks the webhook as inactive. Make sure your server is running and accessible before creating the webhook.

When a calendar event changes on any connected grant, Nylas sends a POST request to your webhook URL. Each notification includes the trigger type and the event data. Here’s a Node.js Express handler that routes each event type to the appropriate CRM operation:

Respond with 200 immediately. Do your processing after sending the response, or push the notification onto a queue. If your endpoint takes too long, Nylas retries the delivery and you end up with duplicates.

Each notification follows the CloudEvents spec. Here’s what an event.created payload looks like:

For event.deleted, the payload is minimal. You only get the event ID, grant ID, calendar ID, and master_event_id (if it was part of a recurring series). The full event details are gone, which is why your sync logic needs to store the Nylas event ID as a foreign key in your CRM.

The webhook payload for event.created and event.updated includes the full event object by default. But if you’ve configured field customizations in your Nylas Dashboard, the payload may only include a subset of fields. In that case, fetch the complete event using the Events API:

The key fields you’ll want for CRM sync:

  • title - The event subject line
  • when.start_time / when.end_time - Unix timestamps for the meeting window
  • participants - Array of attendee objects with email, name, and status
  • location - Physical meeting location, if set
  • conferencing.details.url - Video call link (Zoom, Google Meet, Teams)
  • description - Meeting notes or agenda text
  • organizer - Who created the event
  • status - confirmed, tentative, or cancelled

Transform the Nylas event object into whatever schema your CRM expects. Here’s a reference mapping:

Nylas event fieldCRM fieldNotes
titlemeeting_subjectUse as the CRM record title
when.start_timemeeting_dateConvert from Unix timestamp to your CRM’s date format
when.end_timemeeting_end_dateCalculate duration if your CRM stores it that way
participantsattendeesArray of { email, name } objects
locationmeeting_locationPhysical room or address
conferencing.details.urlmeeting_linkVideo call URL (Zoom, Meet, Teams)
descriptionnotesMay contain HTML from some providers
organizer.emailorganizer_emailThe person who created the event
idexternal_event_idStore this as your dedup key
statusmeeting_statusMap confirmed/tentative/cancelled to CRM statuses

Here’s a mapping function you can adapt:

Then push the record to your CRM:

Match attendees to CRM contacts. Most CRMs let you associate meetings with contact or deal records. After creating the meeting record, loop through the attendees and link each email address to an existing CRM contact. This is where the real value shows up in pipeline reporting.

Recurring events add a layer of complexity. Nylas fires separate webhook notifications for each occurrence of a recurring series, not just one notification for the parent event. Each occurrence has its own unique id, but they all share the same master_event_id that points back to the parent.

Your sync logic should:

  • Store master_event_id alongside each CRM record so you can group occurrences in the UI
  • Treat each occurrence as its own CRM record, since each one may have different participants, times, or statuses (for example, when someone reschedules a single occurrence)
  • Check for the master_event_id field to distinguish standalone events from recurring occurrences

Do not assume recurring events stay consistent. A user might change the time, add a participant, or cancel a single occurrence. Each modification triggers an event.updated webhook for that specific occurrence. Your CRM records should reflect these per-occurrence changes.

A production-quality sync pipeline needs to account for several things that don’t show up in the happy path.

Use the Nylas event id as your deduplication key. Before creating a CRM record, check whether one already exists with that external_event_id. If it does, update instead of creating a duplicate. This single check prevents the most common sync bug.

Nylas guarantees at-least-once delivery, which means you may receive the same notification more than once. Your handler should be idempotent by design. If you receive an event.created notification for an event that already exists in your CRM, treat it as an update.

Nylas normalizes most provider behavior, but a few differences are worth knowing:

  • Google Calendar sends real-time notifications with very low latency (usually under 30 seconds)
  • Microsoft Outlook/Exchange may have slightly higher latency for webhook delivery
  • Google uses cancelled status for deleted single occurrences of recurring events, while Microsoft removes them entirely
  • Event descriptions may contain HTML from some providers and plain text from others

You don’t need provider-specific code branches, but your mapping function should handle both HTML and plain-text descriptions gracefully.

The event.deleted webhook payload only includes the event id, grant_id, calendar_id, and master_event_id. It does not include the full event object, because the event no longer exists. This is why storing the Nylas event ID as a foreign key in your CRM is critical. Without it, you can’t look up which CRM record to remove.

If you’re syncing calendars for hundreds or thousands of users, a single webhook endpoint can get noisy. Consider:

  • Queue incoming webhooks with a message broker (SQS, RabbitMQ, Redis) and process them asynchronously
  • Rate-limit your CRM API calls to avoid hitting your CRM’s rate limits during burst activity (Monday morning calendar updates, for example)
  • Log every notification with the Nylas webhook ID for debugging and replay

Webhooks only fire for changes that happen after you create the subscription. For your first deployment, you need to backfill existing events from each connected grant. Use the List Events endpoint with date range filtering to pull historical data:

Run the backfill before enabling webhooks. This way, when webhooks start firing, your idempotency logic handles any overlap between the backfill window and the first real-time notifications.

A practical approach: backfill the last 90 days of events, then let webhooks handle everything going forward. Adjust the window based on how far back your sales team needs historical meeting data.

You now have a working pipeline that keeps your CRM in sync with calendar events across Google, Microsoft, and Exchange accounts. Here are some next steps to expand the integration: