Call2MeStart free
All posts
Integrations

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.

CTCall2Me Team
June 1, 20263 min read
A small brass desk calendar stand, shot from above

"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.

Build it free first

$5 in credits, no card. Spin up a booking agent and test the whole flow in the browser before you attach a phone number.

Start free on Call2Me →

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:

  1. check_availability(date) → your backend hits Google Calendar's freebusy API, returns open slots.
  2. 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.

See the booking loop live

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.

Build your booking agent →

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.

ShareX / TwitterLinkedIn

Try Call2Me free

Spin up a voice agent in 5 minutes. No credit card required.

Start free trial

Build your voice agent in 10 minutes.

No code. No credit card. Just a phone number.