# Calendar & Booking Integration

> Build a WhatsApp appointment booking flow using the watsi API, webhooks, and your calendar provider.

Source: https://docs.watsi.ai/en/guides/calendar-booking

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

```text
Customer → WhatsApp → watsi webhook → Your backend → Calendar API
```

## Step by step

### Set up a webhook listener

Register a webhook subscription to receive `message.received` events. See the [webhook tutorial](/webhooks/tutorial) for the full setup walkthrough.

```bash
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"]
  }'
```

### 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.

```javascript
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)
})
```

### 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.

```javascript
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)
}
```

### Send available times via WhatsApp

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

```javascript
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.`,
    }),
  })
}
```

### Confirm the booking

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

```javascript
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

- [Webhooks overview](/webhooks)
    Delivery model, headers, and retry behavior.
- [Webhook tutorial](/webhooks/tutorial)
    Step-by-step webhook handler setup.
- [API reference](/reference)
    Full endpoint documentation for messages and conversations.
