WatsiWatsi Docs

Calendar & booking integration

This tutorial shows how to build an appointment booking flow where customers schedule meetings via WhatsApp and events appear in your team's calendar.

Architecture overview

The integration connects three systems:

  1. Watsi — receives WhatsApp messages and sends replies via the API
  2. Your backend — processes booking requests and manages availability
  3. Calendar provider — Google Calendar, Outlook, Cal.com, or any provider with a booking API
Customer → WhatsApp → Watsi webhook → Your backend → Calendar API
Step 1

Set up a webhook listener

Register a webhook subscription to receive message.received events. See the webhook tutorial for the full setup walkthrough.

curl -X POST https://api.watsi.ai/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/watsi",
    "events": ["message.received"]
  }'
Step 2

Detect booking intent

When a message arrives, check whether the customer is asking to book an appointment. You can use keyword matching or an LLM to detect intent.

app.post('/webhooks/watsi', async (req, res) => {
  const event = req.body
  const message = event.data.message

  // Simple keyword detection
  const bookingKeywords = ['book', 'appointment', 'schedule', 'reserve', 'agendar', 'cita']
  const wantsBooking = bookingKeywords.some(kw =>
    message.body.toLowerCase().includes(kw)
  )

  if (wantsBooking) {
    await handleBookingFlow(event.data)
  }

  res.sendStatus(200)
})
Step 3

Fetch available slots

Query your calendar provider for available time slots. This example uses the Google Calendar FreeBusy API, but the same pattern works with any provider.

async function getAvailableSlots(date) {
  const calendar = google.calendar({ version: 'v3', auth })

  const busy = await calendar.freebusy.query({
    requestBody: {
      timeMin: startOfDay(date).toISOString(),
      timeMax: endOfDay(date).toISOString(),
      items: [{ id: CALENDAR_ID }],
    },
  })

  // Compute free 30-minute windows from the busy blocks
  return computeFreeWindows(busy.data, 30)
}
Step 4

Send available times via WhatsApp

Reply to the customer with the available slots. Format times in the customer's timezone when possible.

async function sendAvailableSlots(conversationId, slots) {
  const formatted = slots
    .map((s, i) => `${i + 1}. ${formatTime(s.start)} - ${formatTime(s.end)}`)
    .join('\n')

  await fetch('https://api.watsi.ai/api/v1/messages', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      conversation_id: conversationId,
      body: `Here are the available times:\n\n${formatted}\n\nReply with the number to book.`,
    }),
  })
}
Step 5

Confirm the booking

When the customer replies with their selection, create the calendar event and send a confirmation message.

async function confirmBooking(conversationId, slot, customer) {
  // Create the calendar event
  await calendar.events.insert({
    calendarId: CALENDAR_ID,
    requestBody: {
      summary: `Meeting with ${customer.name}`,
      start: { dateTime: slot.start },
      end: { dateTime: slot.end },
      attendees: [{ email: customer.email }],
    },
  })

  // Send confirmation via Watsi
  await fetch('https://api.watsi.ai/api/v1/messages', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      conversation_id: conversationId,
      body: `Your appointment is confirmed for ${formatTime(slot.start)}. See you then!`,
    }),
  })
}

Production considerations

  • Timezone handling — store and display times in the customer's timezone. Use the contact profile or ask during the flow.
  • Concurrency — check availability again before confirming to prevent double-booking when multiple customers select the same slot.
  • Cancellations — listen for follow-up messages like “cancel” and update the calendar event accordingly.
  • Reminders — use WhatsApp message templates to send appointment reminders outside the 24-hour messaging window.
  • State management — track booking flow state per conversation (e.g. awaiting date, awaiting slot selection, confirmed) in your database.

Related guides