← Integrations
Calendar
Seshn + Google Calendar
Automatically sync bookings to Google Calendar — create events on booking, update on reschedule, remove on cancel.
Install
1npm install googleapisWebhook events
Map booking lifecycle events to calendar operations:
booking.createdCreate a calendar eventbooking.confirmedUpdate event status to confirmedbooking.rescheduledMove event to new timebooking.cancelledDelete the calendar eventbooking.series_createdCreate a recurring calendar eventSetup
Use a Google Cloud service account for server-to-server calendar access. This avoids OAuth flows in your webhook handler.
- Create a project in the Google Cloud Console.
- Enable the Google Calendar API.
- Create a service account and download the JSON key.
- Share your staff calendar with the service account email.
Webhook handler
app/api/webhooks/seshn/route.ts
1import { google } from 'googleapis';2import { createHmac } from 'crypto';34const WEBHOOK_SECRET = process.env.SESHN_WEBHOOK_SECRET!;5const CALENDAR_ID = process.env.GOOGLE_CALENDAR_ID!;67const auth = new google.auth.GoogleAuth({8 keyFile: process.env.GOOGLE_SERVICE_ACCOUNT_KEY_PATH,9 scopes: ['https://www.googleapis.com/auth/calendar'],10});1112const calendar = google.calendar({ version: 'v3', auth });1314export async function POST(req: Request) {15 const body = await req.text();16 const signature = req.headers.get('x-webhook-signature');1718 const expected = 'sha256=' +19 createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex');2021 if (signature !== expected) {22 return new Response('Invalid signature', { status: 401 });23 }2425 const event = req.headers.get('x-webhook-event');26 const payload = JSON.parse(body);2728 switch (event) {29 case 'booking.created':30 case 'booking.confirmed':31 await calendar.events.insert({32 calendarId: CALENDAR_ID,33 requestBody: {34 summary: `${payload.service.name} — ${payload.contact.name}`,35 description: `Booking ${payload.id}\n${payload.seats} seat(s)`,36 start: {37 dateTime: payload.slot.startTime,38 timeZone: payload.location.timezone,39 },40 end: {41 dateTime: payload.slot.endTime,42 timeZone: payload.location.timezone,43 },44 // Use booking ID as event ID for easy lookup later45 id: payload.id.replace(/-/g, '').slice(0, 32),46 },47 });48 break;4950 case 'booking.rescheduled':51 const eventId = payload.id.replace(/-/g, '').slice(0, 32);52 await calendar.events.patch({53 calendarId: CALENDAR_ID,54 eventId,55 requestBody: {56 start: {57 dateTime: payload.newSlot.startTime,58 timeZone: payload.location.timezone,59 },60 end: {61 dateTime: payload.newSlot.endTime,62 timeZone: payload.location.timezone,63 },64 },65 });66 break;6768 case 'booking.cancelled':69 await calendar.events.delete({70 calendarId: CALENDAR_ID,71 eventId: payload.id.replace(/-/g, '').slice(0, 32),72 }).catch(() => {73 // Event may not exist — that's ok74 });75 break;76 }7778 return new Response('OK', { status: 200 });79}Register the webhook
terminal
1curl -X POST https://api.seshn.net/v1/webhooks \2 -H "Authorization: Bearer sk_live_..." \3 -H "Content-Type: application/json" \4 -d '{5 "url": "https://yourdomain.com/api/webhooks/seshn",6 "events": [7 "booking.created",8 "booking.confirmed",9 "booking.rescheduled",10 "booking.cancelled",11 "booking.series_created"12 ]13 }'Next steps
- Add attendees to the event to send Google Calendar invites to customers automatically.
- For
booking.series_created, use Google Calendar's recurrence rules (RRULE) instead of individual events. - Store the Google Calendar event ID in Seshn booking metadata for two-way sync.