API reference
Sync Jobby.dev events to Google Calendar
Use Google Calendar API + Jobby.dev event webhooks to mirror your live-event schedule into the team calendar.
Last updated
Mirror your scheduled Jobby.dev live events into a Google Calendar so the rest of your team knows when you're in interview mode. ~30 minutes including OAuth setup.
What we're building
A small server that subscribes to Jobby.dev event lifecycle webhooks and creates / updates / deletes Google Calendar entries to match. One-way sync (Jobby.dev → Calendar); calendar edits don't propagate back.
1. Set up Google OAuth
- Create a project (or use an existing one).
- Enable the Calendar API.
- Create OAuth credentials with the
https://www.googleapis.com/auth/calendar.eventsscope. - Run the OAuth flow once locally to mint a refresh token. Store the refresh token + client ID + client secret on your server.
2. Subscribe to event webhooks
We need three events to mirror correctly: create, go-live, end.
curl https://jobby.dev/api/v1/webhooks \
-H "Authorization: Bearer $JOBBYDEV_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"target_url": "https://my-server.example.com/calendar-sync",
"events": [
"event.created",
"event.went_live",
"event.ended",
"event.scheduled_window_changed"
]
}'Note: event.*webhooks aren't shipped in the initial v1 cut — they're on the Tier 6 webhook expansion backlog. For now you'll need to poll jobbydev_event_list on a 5-minute cron and diff against your last-known state. Same end result, slightly more complex code.
3. Endpoint stub
import express from "express";
import { google } from "googleapis";
const app = express();
const calendar = google.calendar({ version: "v3", auth: getOAuthClient() });
const CALENDAR_ID = process.env.TEAM_CALENDAR_ID; // e.g. team@yourcompany.com
app.post(
"/calendar-sync",
express.raw({ type: "application/json" }),
async (req, res) => {
if (!verifyJobbydevSignature(req)) return res.status(400).send();
const event = JSON.parse(req.body.toString("utf8"));
switch (event.type) {
case "event.created":
case "event.scheduled_window_changed":
await upsertCalendarEvent(event.data);
break;
case "event.ended":
await deleteCalendarEvent(event.data.event_id);
break;
}
res.status(200).send("ok");
},
);4. Upsert helper
async function upsertCalendarEvent(data) {
const summary = `Jobby.dev: ${data.title}`;
const description = `Live event on Jobby.dev — ${data.role_count} role(s) attached.\nhttps://jobby.dev/events/${data.slug}`;
// Use Jobby.dev's event_id as the Google Calendar extended property
// for a stable foreign key
const existing = await findExistingEvent(data.event_id);
const eventBody = {
summary,
description,
start: { dateTime: data.starts_at, timeZone: "UTC" },
end: { dateTime: data.ends_at, timeZone: "UTC" },
extendedProperties: {
private: { jobbydev_event_id: data.event_id },
},
};
if (existing) {
await calendar.events.update({
calendarId: CALENDAR_ID,
eventId: existing.id,
requestBody: eventBody,
});
} else {
await calendar.events.insert({
calendarId: CALENDAR_ID,
requestBody: eventBody,
});
}
}5. Idempotency
Critical here. The webhook subsystem retries on timeout, so you could process the same event.created twice. Using extendedProperties to find the existing entry by jobbydev_event_id (rather than naively inserting) makes the upsert idempotent — the second delivery sees the entry and updates instead of duplicating.
6. Test
Schedule a Jobby.dev event with jobbydev_create_event and confirm a Calendar entry appears within ~5 seconds. Update the window; confirm the Calendar entry updates. End the event; confirm the Calendar entry is removed.
Limitations
- One-way only. Calendar edits don't propagate to Jobby.dev.
- Time zones: we send UTC. Google Calendar respects the per-user timezone for display.
- Attendees: not mirrored. The Calendar entry is informational — actual recruiter access to the live event is gated through Jobby.dev.