Connect a voice AI agent to Google Calendar (real booking, not a demo)
Let your phone agent check availability and book appointments straight into Google Calendar. Here's the function-call + webhook pattern that actually works in production.
"Can your AI agent actually book the appointment?" is the question that turns a voice demo into a paying use case. A bot that says "I'll have someone call you back" is a toy. A bot that checks your real Google Calendar, offers open times, and writes the booking — that replaces a receptionist.
Here's the pattern that works in production, end to end.
$5 in credits, no card. Spin up a booking agent and test the whole flow in the browser before you attach a phone number.
The architecture (one diagram in words)
The model never touches Google. It calls two tools you expose, and your backend — which holds the Google OAuth token — does the real work:
check_availability(date)→ your backend hits Google Calendar'sfreebusyAPI, returns open slots.book_slot(start, name, phone)→ your backend re-checks the slot is still free, then creates the event.
The agent's job is conversation. Your backend's job is truth. That separation is the whole trick.
Step 1 — expose the tools to your agent
In Call2Me, an agent's tools are function definitions with an HTTP webhook behind them. Define two:
{
"name": "check_availability",
"description": "Return open appointment slots for a given date.",
"parameters": { "date": "string (YYYY-MM-DD)" },
"webhook": "https://your-backend.com/tools/availability"
}
{
"name": "book_slot",
"description": "Book an appointment. Confirm only after the caller agrees to a specific time.",
"parameters": { "start": "string (ISO 8601)", "name": "string", "phone": "string" },
"webhook": "https://your-backend.com/tools/book"
}
When the agent decides to call a tool, Call2Me POSTs to your webhook and speaks the response back to the caller. Same request/response shape you'd use for any function call.
Step 2 — check availability against the real calendar
Your availability handler asks Google what's busy and returns what's open:
from googleapiclient.discovery import build
def availability(date: str) -> dict:
cal = build("calendar", "v3", credentials=load_google_creds())
body = {
"timeMin": f"{date}T09:00:00Z",
"timeMax": f"{date}T17:00:00Z",
"items": [{"id": "primary"}],
}
busy = cal.freebusy().query(body=body).execute()["calendars"]["primary"]["busy"]
open_slots = subtract_busy(business_hours(date), busy) # your slotting logic
return {"slots": [s.isoformat() for s in open_slots]}
The agent now offers real times: "I've got 2pm, 3:30, or 4 — which works?"
Step 3 — book it (and handle the race)
The booking handler re-checks at write time, so two callers can't grab the same slot:
def book(start: str, name: str, phone: str) -> dict:
cal = build("calendar", "v3", credentials=load_google_creds())
if not still_free(cal, start):
return {"ok": False, "speak": "That time just filled — I have the next slot open instead."}
cal.events().insert(calendarId="primary", body={
"summary": f"Appointment — {name}",
"description": f"Booked by voice agent. Caller: {phone}",
"start": {"dateTime": start}, "end": {"dateTime": plus_30min(start)},
}).execute()
return {"ok": True, "speak": "You're booked. You'll get a confirmation by text."}
The speak field is what the agent says next — you control the recovery line, the
model handles the conversation around it.
Why not let the model call Google directly?
Two reasons, both about safety:
- Credentials stay off the model. The Google OAuth token lives in your backend, never in a prompt or a tool the model can introspect.
- Validation lives in one place. Business hours, buffer times, "no bookings within 2 hours," double-booking checks — all enforced server-side, so the agent literally cannot book something invalid no matter what the caller says.
What you end up with
A phone number that answers 24/7, checks your actual calendar, offers real times, books the appointment, and confirms — without a human. For a clinic, salon, garage, or restaurant, that's the difference between a missed call and a captured booking.
The fastest way to believe it is to watch your own calendar get a new event from a test call. Free to try — $5 in credits, no card.
Want it wired through a no-code workflow instead of your own backend? The same two tools work as Zapier / Make steps — same contract, just a different runner.
Frequently asked
Q.Does the agent talk to Google Calendar directly?
No — and it shouldn't. The agent calls a function (a tool) you expose; your backend holds the Google OAuth token and talks to the Calendar API. This keeps credentials off the model and lets you add validation (business hours, buffers, double-booking checks) in one place.
Q.Can it check availability before booking?
Yes. Expose two tools: check_availability(date) and book_slot(start, name, phone). The agent calls the first to offer real open times, then the second to confirm. That two-step flow is what makes it feel like a real receptionist instead of a form.
Q.What happens if the slot was just taken?
Your book_slot handler does a freebusy check against the calendar at write time and returns a clear error the agent can speak ('that 3pm just filled — I have 3:30 or 4'). The model handles the recovery; you handle the truth source.
Keep reading
All posts- Integrations
Voice AI + Zapier and Make: automate phone calls without writing a backend
Trigger AI phone calls from any app, and pipe call outcomes anywhere — Sheets, CRM, Slack — using Zapier or Make. No server required. Here's the exact setup.
Jun 1, 20262 min - AI Agents
Give your AI agent a phone: turning chat agents into voice agents
Slack, Gmail, Notion — your agent can already do everything except pick up the phone. Here's how to add a real phone-call tool in 8 lines, and what changes when it can.
May 6, 20266 min - Comparison
The Bland AI alternative for teams who want to own their stack
Bland is fast and polished. But if you want a built-in knowledge base, white-label, and pricing you can read without a spreadsheet, here's the honest comparison.
May 31, 20264 min