Sync calendar bookings with Calendly
Bridge Google Calendar and Calendly — Northwind bookings on either side appear on both.
Published Jan 7, 2026
Northwind takes client calls through Calendly, but the team runs its day off Google Calendar. When a prospect books a slot, it lands in Calendly — and then nowhere else until someone notices and copies it across. Double-bookings and missed calls follow, because the calendar everyone actually looks at does not know about the booking.
This script bridges the two. It reads upcoming events from the Calendly API and creates a matching event on the Google Calendar the team lives in. It checks for an existing copy first, so running it repeatedly never produces duplicates. Put it on a frequent trigger and every Calendly booking shows up where it will be seen, within minutes.
What you’ll need
- A Calendly account with the API enabled, and a personal access token.
- Your Calendly user URI — the
https://api.calendly.com/users/...URL that identifies your account. The fastest way to find it is a one-off call to the/users/meendpoint. - That token saved as
CALENDLY_TOKENand the URI asCALENDLY_USER_URIin Script Properties — see Store API keys and secrets securely. - Edit access to the Google Calendar you want the bookings to land on. The script uses your default calendar.
The script
// Calendly credentials, kept out of the code.
const CALENDLY_TOKEN = PropertiesService.getScriptProperties()
.getProperty('CALENDLY_TOKEN');
const CALENDLY_USER_URI = PropertiesService.getScriptProperties()
.getProperty('CALENDLY_USER_URI');
// Only sync events that are still active — skip ones the invitee cancelled.
const EVENT_STATUS = 'active';
/**
* Reads upcoming Calendly events and creates a matching Google Calendar
* event for each one that is not already there.
*/
function pullCalendlyBookings() {
// 1. Ask Calendly for this user's active scheduled events.
const url = 'https://api.calendly.com/scheduled_events' +
'?user=' + encodeURIComponent(CALENDLY_USER_URI) +
'&status=' + EVENT_STATUS;
const res = JSON.parse(UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + CALENDLY_TOKEN },
muteHttpExceptions: true,
}).getContentText());
// 2. Bail out early if there are no bookings to sync.
if (!res.collection || !res.collection.length) {
Logger.log('No Calendly events returned — nothing to do.');
return;
}
const cal = CalendarApp.getDefaultCalendar();
let created = 0;
// 3. Walk each Calendly event and mirror it onto Google Calendar.
for (const e of res.collection) {
const start = new Date(e.start_time);
const end = new Date(e.end_time);
// 4. Look for an event with the same name in that exact window.
// If one exists, this booking is already synced — skip it.
const existing = cal.getEvents(start, end, { search: e.name });
if (existing.length) continue;
// 5. No copy found, so create the event on Google Calendar.
cal.createEvent(e.name, start, end, {
description: 'Synced from Calendly\n' + e.uri,
});
created++;
}
Logger.log('Created ' + created + ' new calendar event(s) from Calendly.');
}
How it works
pullCalendlyBookingscalls the Calendlyscheduled_eventsendpoint, scoped to your user URI and filtered to active events so cancelled slots are ignored.- If the response collection is empty, it logs a message and stops — no calendar work to do.
- It grabs your default Google Calendar and loops over each Calendly event,
converting the ISO start and end strings into
Dateobjects. - Before creating anything, it searches the calendar for an event with the same name in that exact time window. A match means the booking was synced on an earlier run, so it is skipped. This check is what makes the script safe to run on a tight schedule.
- When no copy exists, it creates the event and tags the description with the Calendly URI, so anyone looking at the calendar knows where it came from.
Example run
Say Calendly returns two upcoming bookings:
| name | start_time | end_time |
|---|---|---|
| Discovery call — Acme | 2026-01-12T14:00:00Z | 2026-01-12T14:30:00Z |
| Strategy review — Globex | 2026-01-13T09:00:00Z | 2026-01-13T10:00:00Z |
On the first run, both are new, so the log reads:
Created 2 new calendar event(s) from Calendly.
Two events now sit on Google Calendar, each described Synced from Calendly.
On the next run nothing has changed, the name-and-window search finds both, and
the log reads Created 0 new calendar event(s) from Calendly. — no duplicates.
Trigger it
Bookings should appear quickly, so run this often:
- In the Apps Script editor, open Triggers (the clock icon).
- Click Add Trigger.
- Choose
pullCalendlyBookings, a Time-driven source, a Minutes timer, and Every 15 minutes.
Fifteen minutes is a good balance — fresh enough that nobody misses a call, infrequent enough to stay well inside Apps Script’s daily quotas.
Watch out for
- The duplicate check matches on name plus time window. If two distinct Calendly events share a name and overlap, the second is treated as already synced and skipped. For high volume, store synced Calendly URIs in Script Properties and check against those instead.
- This is one-way: Calendly to Google Calendar. An event you create directly in Google Calendar is not pushed back to Calendly, and editing the Google copy does not change the Calendly booking.
- The script reads one page of events. Calendly paginates with a
paginationblock; a busy account needs a loop that followspagination.next_pageuntil it is null. - Cancellations are not propagated. Filtering to
status=activestops cancelled slots being created, but a booking cancelled after it was synced still sits on Google Calendar. Add a cleanup pass if that matters. - Times come from Calendly as UTC ISO strings.
new Date()parses them correctly, but the event displays in your calendar’s timezone — check it shows the hour you expect before trusting it.
Related
Connect to an air-quality and weather feed
Build a Northwind environmental dashboard — current London AQI plus 5-day forecast.
Updated Dec 30, 2025
Build a podcast and media stats tracker
Pull Northwind's podcast download numbers across platforms into a single sheet.
Updated Dec 10, 2025
Track real-estate listings for new matches
Monitor property feeds for Northwind office hunts — alert when a match appears.
Updated Nov 28, 2025
Translate columns with a translation API
Localise Northwind text in bulk without manual work — via Google Translate or DeepL.
Updated Nov 24, 2025
Build a job-listings aggregator
Collect Northwind-relevant postings via public job-board APIs into a sheet for the team.
Updated Nov 20, 2025