The problem

Bumi Naturals needed customers to be able to book appointments directly through WhatsApp instead of relying on calls or DMs that got missed after hours.

Why a basic setup wasn't enough

A booking flow like this needs to tell the difference between a customer replying with contextual information, like an email address, and a customer starting a fresh booking request. A single workflow can't reliably make that distinction, which meant session state had to be tracked properly instead of guessed at.

What I built

A WhatsApp-based booking system connected to Cal.com, so customers check availability and book appointments through a normal WhatsApp conversation. Session state is tracked in Airtable so the system always knows where a given customer is in the conversation.

Architecture

Incoming WhatsApp message → parsed and routed by message type → intent classified → live availability pulled directly from the Cal.com API → booking confirmed → session state updated in Airtable.

Tools used

n8n, WhatsApp Cloud API, Cal.com, Airtable

Challenges

Two problems came up mid-build that don't show up in tutorials. Meta wouldn't allow changing the webhook callback URL, so the fix had to work within the existing endpoint instead of forcing a migration. Separately, Cal.com's stated availability didn't match what displayed in WhatsApp because of a timezone mismatch, solved by querying the Cal.com API directly instead of assuming a timezone conversion. There's also a known bug in n8n's dedicated WhatsApp Trigger node, worked around by building the webhook handling with two generic Webhook nodes instead.

What I'd improve next

Finish the three-workflow Orchestrator pattern so session state routing is fully separated from the booking intent classifier. That's the current setup's main limitation, it works end to end today, but the restructure will make it more reliable as usage grows.

Status

Live and in production for a paying client.