Supabase + OpenAI: smoother appointment scheduling
Your appointment schedule shouldn’t feel like a game of whack-a-mole. One cancellation comes in, someone reschedules, a new patient calls, and suddenly you’re juggling tabs, notes, and “Did we already book that slot?” panic. Supabase scheduling automation fixes the messy middle.
This hits front-desk teams hardest, but clinic owners and operations managers feel it too. The outcome is simple: fewer double bookings, fewer follow-up calls, and faster answers for patients without adding headcount.
Below, you’ll see exactly how the n8n workflow routes each request (book, reschedule, cancel, insurance, availability), updates Supabase, and returns a clean response your phone system can read back.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Supabase + OpenAI: smoother appointment scheduling
flowchart LR
subgraph sg0["Generate Availabilit Flow"]
direction LR
n0["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Webhook"]
n1@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Switch", pos: "b", h: 48 }
n2["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify Appointment Time"]
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Find Patient (Booking)"]
n4["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Find Patient (Reschedule)"]
n5["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Find Patient (Cancel)"]
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Find Patient (List Appointme.."]
n7["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Find Insurance Record"]
n8@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Check Time Availability", pos: "b", h: 48 }
n9["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Get Existing Appointments"]
n10["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Create Appointment"]
n11@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Time Slot Already Taken?", pos: "b", h: 48 }
n12["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify Appointment Conflict"]
n13["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Verify Patient Phone Number"]
n14["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Create Appointment Record"]
n15["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Fetch Patient's Current Appo.."]
n16["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Check Doctor Availability fo.."]
n17["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Delete Appointment Record"]
n18["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify Cancelation Success"]
n19@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Check Insurance Status", pos: "b", h: 48 }
n20["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify Insurance Accepted"]
n21["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify Insurance Rejected"]
n22@{ icon: "mdi:cog", form: "rounded", label: "Summarize Appointment Data", pos: "b", h: 48 }
n23["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Fetch Patient Appointments"]
n24@{ icon: "mdi:cog", form: "rounded", label: "Aggregate Patient Appointments", pos: "b", h: 48 }
n25["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Send Summarized Appointment .."]
n26@{ icon: "mdi:brain", form: "rounded", label: "LLM Engine", pos: "b", h: 48 }
n27@{ icon: "mdi:robot", form: "rounded", label: "Generate Availability With AI", pos: "b", h: 48 }
n28["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Send Availability Response"]
n29@{ icon: "mdi:swap-vertical", form: "rounded", label: "Fetch Doctor’s Appointments", pos: "b", h: 48 }
n30["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Fetch All Doctors"]
n31@{ icon: "mdi:cog", form: "rounded", label: "Aggregate Doctors Data", pos: "b", h: 48 }
n32@{ icon: "mdi:cog", form: "rounded", label: "Summarize Doctors Data", pos: "b", h: 48 }
n33["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Send Doctors List"]
n34["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Confirm Rescheduled Appointm.."]
n35["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/supabase.svg' width='40' height='40' /></div><br/>Update Appointment to New Slot"]
n36["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Notify: Time Slot Taken"]
n37@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Is Reschedule Possible?", pos: "b", h: 48 }
n1 --> n3
n1 --> n4
n1 --> n5
n1 --> n6
n1 --> n7
n1 --> n27
n1 --> n30
n0 --> n1
n26 -.-> n27
n30 --> n31
n10 --> n9
n7 --> n23
n5 --> n17
n31 --> n32
n19 --> n20
n19 --> n21
n3 --> n8
n32 --> n33
n8 --> n9
n8 --> n10
n37 --> n36
n37 --> n35
n11 --> n12
n11 --> n13
n14 --> n2
n17 --> n18
n4 --> n15
n9 --> n11
n23 --> n24
n22 --> n25
n13 --> n14
n29 -.-> n27
n27 --> n28
n24 --> n22
n35 --> n34
n6 --> n19
n15 --> n16
n16 --> n37
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n27 ai
class n26 aiModel
class n1,n8,n11,n19,n37 decision
class n0,n2,n12,n18,n20,n21,n25,n28,n33,n34,n36 api
classDef customIcon fill:none,stroke:none
class n0,n2,n3,n4,n5,n6,n7,n9,n10,n12,n13,n14,n15,n16,n17,n18,n20,n21,n23,n25,n28,n30,n33,n34,n35,n36 customIcon
The Problem: Scheduling Requests Create Constant Interruptions
Appointment management is “small” work that somehow eats the whole day. A patient calls to book, you check the calendar, ask for their details, verify insurance, then document everything in the right place. Five minutes becomes fifteen. Now multiply that by reschedules, cancellations, and the inevitable “What times do you have next week?” calls. The real cost isn’t only time. It’s errors, missed context, and the stress of keeping the schedule accurate while the phone keeps ringing.
It adds up fast. And it usually breaks in the same predictable spots:
- You end up re-checking availability over and over, because last-minute changes make your notes unreliable.
- Reschedules can quietly create double bookings when the “new time” isn’t validated against existing appointments.
- Insurance questions pull you away from higher-value work, even when the answer is a simple accepted/not accepted lookup.
- Patient identification gets messy when you rely on memory instead of using the phone number as a consistent key.
The Solution: Route Every Request, Update Supabase, Respond Instantly
This workflow turns your phone-based assistant into a reliable scheduling operator. A request comes in through a webhook (from your voice or phone system), then n8n uses intent routing to decide what the patient is trying to do: book, reschedule, cancel, check insurance, list appointments, or ask for availability. From there, Supabase becomes the source of truth. The workflow looks up the patient by phone number, checks or creates their profile, validates the time slot, and writes the appointment change back to the database. Finally, it returns a clear webhook response that your phone system reads to the patient, so they get an answer without your team scrambling.
The flow starts with an incoming request and a routing decision. Then Supabase lookups and checks prevent conflicts (including “slot already taken” scenarios). Availability requests are handled by an AI agent that generates clean 60-minute slot options based on the doctor’s existing bookings, and the workflow responds immediately.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say your clinic handles about 20 scheduling-related calls a day (new bookings, reschedules, cancellations, and “what’s available?”). Manually, even 8 minutes per call is roughly 2.5 hours of pure admin time, and that’s before you fix mistakes. With this workflow, the call triggers a webhook instantly, Supabase checks run in the background, and the patient gets a response in under a minute for most requests. That’s about 2 hours back on a normal day, plus fewer “sorry, that slot is gone” follow-ups.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Supabase for patient, doctor, and appointment tables
- OpenAI to generate readable availability responses
- OpenAI API key (get it from the OpenAI dashboard)
Skill level: Intermediate. You’ll map table/column names in Supabase and paste credentials into the right n8n nodes.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A request hits your webhook. Your phone or voice assistant sends the patient’s message (and usually the phone number) to n8n through the Incoming Webhook Trigger.
The workflow routes by intent. The Switch node (“Route by Tool Intent”) decides if this is a booking, reschedule, cancellation, insurance check, appointment listing, availability request, or a doctor list request.
Supabase becomes the referee. Patient records are retrieved by phone number, new patients can be created, and appointment records are inserted, updated, or removed depending on the action. Conflict checks (like “slot occupied” or “reschedule feasible”) happen before anything is confirmed.
A clean response goes back to the caller. Respond-to-webhook nodes send the exact confirmation your phone system reads out loud, including booking confirmations, cancellation confirmations, availability options, and insurance accepted/rejected messages.
You can easily modify appointment duration (the workflow uses 60-minute slots) to shorter visits based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Webhook Trigger
This workflow starts when an external system posts appointment requests into your webhook.
- Add and open Incoming Webhook Trigger.
- Set HTTP Method to
POST. - Set Path to
3beec299-79db-453e-8986-b1b98e82209d. - Set Response Mode to
responseNodeto allow downstream respondToWebhook nodes to reply.
Execution Flow: Incoming Webhook Trigger → Route by Tool Intent.
Step 2: Connect Supabase Data Sources
Supabase powers patient records, appointment data, and doctor catalogs. Connect credentials once, then configure each Supabase node.
- Open each Supabase node (15 total, including Lookup Patient for Booking, Retrieve Existing Bookings, Insert Appointment Record, Retrieve All Doctors, and Update Booking Slot).
- Credential Required: Connect your
supabaseApicredentials. - In Lookup Patient for Booking, set Table to
dental_patients_canadaand filter phone_number with={{ $json.body.args.phone_number }}. - In Retrieve Existing Bookings, set Table to
dental_appointments_canada, Operation togetAll, and add filters:appointment_date = {{ $('Incoming Webhook Trigger').item.json.body.args.start }}anddoctor_id = {{ $('Incoming Webhook Trigger').item.json.body.args.doctor_id }}. - In Create Patient Profile, set Table to
dental_patients_canadaand map fields:name,phone_number,email, anddoctor_idfrom Incoming Webhook Trigger. - In Insert Appointment Record, set Table to
dental_appointments_canadaand map fields:patient_id = {{ $json.id }},doctor_id = {{ $('Incoming Webhook Trigger').item.json.body.args.doctor_id }},appointment_date = {{ $('Incoming Webhook Trigger').item.json.body.args.start }},reason = {{ $('Incoming Webhook Trigger').item.json.body.args.service_type }}. - In Retrieve All Doctors, set Table to
dental_doctors_canadaand Operation togetAll.
Step 3: Set Up Intent Routing and Booking Logic
The workflow uses a switch to route requests into booking, rescheduling, cancellation, insurance checks, listings, availability, or doctor catalogs.
- Open Route by Tool Intent and confirm rules use
={{ $json.body.args.tool_name }}with values:set_appointment,reschedule,cancel,insurance_check,get_appointmnets_list,get_availability, andget_doctors_list. - Confirm the booking path: Route by Tool Intent → Lookup Patient for Booking → Validate Time Availability → Retrieve Existing Bookings → Check Slot Occupied.
- In Validate Time Availability, keep the condition
={{ $('Lookup Patient for Booking').item.json.id }}to check if a patient exists. - In Check Slot Occupied, keep the condition
={{ $('Retrieve Existing Bookings').item.json.id }}to detect conflicts before booking. - Ensure rescheduling follows: Lookup Patient for Reschedule → Fetch Current Appointment → Check Doctor New Slot → Check Reschedule Feasibility → Update Booking Slot or Respond Slot Unavailable.
- Ensure cancellation follows: Lookup Patient for Cancel → Remove Appointment Record → Send Cancellation Confirmation.
get_appointmnets_list) will bypass routing and leave the workflow without a response.Step 4: Set Up AI Availability and Language Model
This path uses an AI agent to compute availability using stored appointment data.
- Open LLM Chat Engine and select model
gpt-4o. - Credential Required: Connect your
openAiApicredentials in LLM Chat Engine. - Open Generate Availability via AI and keep the prompt as defined, including
{{ $json.body.args.start }},{{ $json.body.args.end }}, and{{ $json.body.args.doctor_id }}. - Open Fetch Doctor Bookings and confirm it queries
dental_appointments_canadawithdoctor_id = {{ $json.body.args.doctor_id }}.
OpenAI credentials are connected on LLM Chat Engine, which is the language model for Generate Availability via AI. Fetch Doctor Bookings is a tool used by the agent and should share the same supabaseApi credentials.
Step 5: Configure Listing, Insurance, and Summary Responses
These branches return structured results to the webhook caller.
- For insurance checks, connect Lookup Patient for Listing → Validate Insurance Status and keep the condition
={{ $json.id }}to decide between Respond Insurance Accepted and Respond Insurance Rejected. - For appointment lists, ensure Fetch Patient Bookings → Aggregate Booking Records → Summarize Booking Data → Send Appointment Summary is connected.
- For doctor catalogs, ensure Retrieve All Doctors → Aggregate Doctors Records → Summarize Doctors Info → Send Doctors Catalog is connected.
- In Send Appointment Summary, keep Response Body as
={ ... "appointments_list": {{ $json.data }} }. - In Send Doctors Catalog, keep Response Body as
={ ... "doctors_list": {{ $json.data }} }.
Step 6: Configure Booking and Cancellation Responses
These responses confirm time slots, conflicts, reschedules, and cancellations back to the caller.
- In Send Appointment Time Reply, keep Response Body set to
={ ... "date": "{{ $json.date }}", "name": "{{ $json.name }}" }. - In Respond Slot Conflict, keep the static message to signal the time is already booked.
- In Confirm Reschedule Reply, keep the dynamic message using
{{ $('Route by Tool Intent').item.json.body.preferred_date }}. - In Send Cancellation Confirmation, keep the static cancellation message as defined.
- Ensure the booking flow reaches Verify Patient Phone → Insert Appointment Record → Send Appointment Time Reply when no slot conflicts exist.
Step 7: Test and Activate Your Workflow
Run a full end-to-end test to validate each intent path and response.
- Click Execute Workflow and send a test POST to the Incoming Webhook Trigger URL with a sample
tool_nameand arguments. - Confirm the switch routes to the correct branch (e.g., booking path reaches Send Appointment Time Reply, cancel path reaches Send Cancellation Confirmation).
- Verify Supabase writes by checking new rows in
dental_appointments_canadaanddental_patients_canadafor booking tests. - For availability, confirm the AI path returns a list of available time slots in Send Availability Reply.
- When satisfied, toggle the workflow to Active for production use.
Common Gotchas
- Supabase credentials can expire or need specific permissions. If things break, check your Supabase project API settings and table permissions first.
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Default prompts in AI nodes are generic. Add your brand voice early or you’ll be editing outputs forever.
Frequently Asked Questions
About 60–90 minutes if your Supabase tables are ready.
No. You’ll connect Supabase and OpenAI, then map a few fields in n8n. The only “technical” part is making sure your table and column names match what the workflow expects.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in OpenAI API costs (often a few dollars a month at typical clinic volume).
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, but plan to test it carefully. The quickest win is adjusting the availability logic that generates 60-minute slots, then making sure the booking and rescheduling checks still validate conflicts correctly. You can also tweak the wording in the webhook response nodes so the assistant reads the new duration naturally. Most clinics also customize working hours and “buffer time” rules once they see this running live.
Usually it’s an invalid or rotated Supabase API key, or the role you’re using doesn’t have permission to read/write the appointments tables. Double-check the Supabase credentials inside the Supabase nodes, then confirm the table names and columns match exactly. If failures happen only during busy periods, you may also be hitting rate limits or returning empty lookups for unknown phone numbers.
A lot. On n8n Cloud, capacity depends on your plan’s monthly executions, and most clinics fit comfortably on a starter tier. If you self-host, there’s no execution limit, and the practical ceiling becomes your server size and how quickly Supabase and OpenAI respond.
For this use case, usually yes, because you need branching logic (booking vs reschedule vs cancellation), database checks, and consistent responses that a phone system can read without weird formatting. n8n handles those “if this, then that, unless…” paths without turning into a fragile spaghetti Zap. Supabase is also a first-class database layer here, not just a place to dump rows. Zapier or Make can still work for simple appointment capture, though, especially if you only need a two-step flow. If you’re on the fence, Talk to an automation expert and we’ll sanity-check the best path in 15 minutes.
Once this is in place, scheduling stops being a constant interruption. The workflow handles the repetitive confirmations and database updates, so your team can focus on patients standing in front of them.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.