Skip to content
watsi

Use cases

Calendar & Booking Integration

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

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 by step

  1. 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"]
      }'
  2. 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)
    })
  3. 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)
    }
  4. 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.`,
        }),
      })
    }
  5. 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.