Skip to content

Chatbot Architecture

The WhatsApp conversation engine that powers patient self-service booking.

sequenceDiagram
actor P as Patient
participant WA as WhatsApp Client
participant Meta as Meta Cloud API
participant BE as ClinicFlow Backend
participant Redis as Redis (Cache)
participant Claude as Claude Haiku 4.5
participant DB as Supabase PostgreSQL
P->>WA: "Quiero agendar cita"
WA->>Meta: Send message
Meta->>BE: POST /webhooks/whatsapp
BE->>BE: HMAC-SHA256 verify
BE->>Redis: Check 24h dedup
Redis-->>BE: Not duplicate
BE->>Redis: Acquire lock (clinicId, phone)
BE->>DB: Load/create chat_session
BE->>DB: Load clinic context + availability
BE->>DB: Check bot_enabled? not blocked?
BE->>Claude: NLU request (context + slots + history)
Claude-->>BE: { action: "ask", collected: {...}, reply: "..." }
BE->>Meta: Send reply text
Meta->>WA: Deliver message
WA->>P: "!Claro! ?Para que dia?"
Note over P,BE: ... conversation continues ...
P->>WA: "Manana en la tarde"
WA->>Meta: Send message
Meta->>BE: POST /webhooks/whatsapp
BE->>Claude: NLU with updated context
Claude-->>BE: { action: "confirm", readyToConfirm: true, ... }
BE->>DB: Verify slot still free
BE->>DB: INSERT appointment
BE->>BE: Enqueue reminder jobs (24h + 1h)
BE->>Meta: Send confirmation card
Meta->>WA: Deliver interactive card
WA->>P: "? Listo — manana 3:30 PM"
BE->>Redis: Release lock
IDLE → COLLECTING_DATE → COLLECTING_TIME → COLLECTING_NAME
→ CONFIRMING → CONFIRMED
↓ (any state)
RESET → IDLE

Each state is tracked in chat_sessions.current_state with collected fields in chat_sessions.collected_fields (JSONB).

The system prompt injects live clinic context:

  • Clinic name and address
  • Doctor names and specialties
  • Real-time availability slots for the requested date
  • FAQ entries for keyword matching
Button ID Action
CONFIRM_BOOKING Short-circuit: write appointment, send confirmation
EDIT_BOOKING Reset to COLLECTING_DATE
CANCEL_BOOKING Cancel appointment flow
RESET Reset entire conversation
RECALL_REPLY_* Dental recall campaign responses

Short-circuits skip the Claude API call entirely — faster and cheaper.