This guide shows a clean, production-ready way to connect Aigent360 with Cal.com so your assistant can (1) check real-time availability and (2) create confirmed bookings with solid validation and error handling.
Use the steps in this guide instead of Cal.com’s default integration. The out-of-the-box option often doesn’t pass the caller’s email into the booking, which means missed confirmations, calendar invites, and follow-ups. Our approach walks you through a simple three-step flow (Webhook → a small Zapier step → done) that reliably captures the caller’s details every time—no coding background required.
The result: clean appointments, accurate contact info, and smoother reminders and CRM updates.
You’ll use two simple building blocks:
In Aigent360, a GET slots action that calls https://api.cal.com/v2/slots
with the right headers, time window, eventTypeId
, and timeZone
.
In Zapier, a Webhooks (Catch Hook) → Code by Zapier (JavaScript) flow that receives a flat JSON from Aigent360, validates the inputs, constructs the exact Cal.com booking payload (including the nested attendee
object), and posts it to /v2/bookings
.
This approach keeps your Aigent360 configuration streamlined while centralizing transformation, input checks (RFC-5322 emails, IANA time zones, E.164 phone numbers), and clear error messages inside the Code step you control. It also normalizes the start time to UTC Z, aligning with Cal.com’s expectations.
What you’ll build
Aigent360 custom action to fetch slots (headers, params, and user-friendly messages).
Aigent360 custom action to send a minimal JSON (name, email, phoneNumber, start, timeZone, eventTypeId) to your Zapier Catch Hook.
Zapier Code (JS) that validates inputs, builds the nested attendee
payload, calls Cal.com, and returns a tidy response (meeting_url
, start_utc
, booking_id
) back to Aigent360.
What you’ll need
Cal.com API key, eventTypeId
, and API versions (slots vs. bookings).
Zapier (not free account): access to Webhooks by Zapier and Code by Zapier (JavaScript).
Aigent360 access to create and wire Custom Actions.
How to use this guide
Follow the steps in order and add your screenshots where indicated (Aigent360 actions, Zapier Catch Hook, Code step, and a test run). The checklists at the end help you verify readiness for go-live. By the finish, your agent will retrieve availability from Cal.com and book confirmed slots through a robust, well-validated Zap—ready for real users.
Security reminder: Never hard‑code live secrets in public UIs or docs. Use environment variables, Zapier Vault/Secrets, or Aigent360’s encrypted auth store. Replace any example tokens below with your own.
Goal: Let your Aigent360 assistant fetch open time slots for a specific eventTypeId
within a date range and time zone so the user can pick a time. In Aigent360, start by going to Actions → Custom actions → Create a new custom action.
1) Create the action in Aigent360
Action name: checking_available_slots
Description: Invoke when the caller wants to find available slots in a calendar to book an appointment or consultation.
Method: GET
Endpoint: https://api.cal.com/v2/slots
Headers
Authorization: Bearer <CAL_API_KEY>
cal-api-version: 2024-09-04
Authorization
Type: Custom
Store <CAL_API_KEY>
securely in Aigent360 (do not hard-code in prompts/logs).
2) Query parameters (map these as variables)
start
(string, ISO-8601 UTC) — e.g., 2025-08-29T00:00:00Z
end
(string, ISO-8601 UTC) — e.g., 2025-08-30T23:59:59Z
timeZone
(string, IANA tz) — e.g., America/Los_Angeles.
If your workflow is constrained to a fixed time zone, you can predefine this value in the action; otherwise, ask the caller which time zone they’re in.
eventTypeId
(integer) — your Cal.com event type ID
format
(optional) — only if you need a specific slot format
Tip: Use UTC (
Z
) forstart
/end
and convert to local time only for display.
3) Agent messages (optional)
Start: “Let me check the available time slots for your booking…”
Delay (7s): “This is taking a little longer than usual — I’m still fetching the available slots, please hold on.”
Failure: “Sorry, I couldn’t fetch available slots. Try again or adjust your requested range.”
4) Validation & best practices
Ensure start
< end
and both are valid ISO-8601 datetimes with Z
.
Keep the search window reasonable (e.g., 1–14 days) to avoid oversized responses.
Always send a clear IANA time zone (timeZone
) so slot times align with the user’s locale.
Cache/limit repeated calls in the same dialog turn to reduce latency.
5) Set up screenshots for reference.
Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
Slide 6
Goal: Create confirmed bookings by posting a flat JSON from Aigent360 to Zapier Webhooks, then using Code by Zapier (JavaScript) to validate inputs, build the nested attendee
object, and call POST https://api.cal.com/v2/bookings
with robust error handling.
In Aigent360, start by going to Actions → Custom actions → Create a new custom action.
Action name: booking_appointment
Description: Invoke when the user confirms interest in booking and selects a preferred slot from the available options.
Method: POST
Endpoint: (your Zapier Webhook URL)
Headers
Content-Type: application/json
Authorization
Type: None (posting to Zapier). Keep Cal.com secrets inside Zapier, not in Aigent360.
Body
{
"name": "<name>",
"email": "<email>",
"phoneNumber": "<phoneNumber>",
"start": "<start>",
"timeZone": "//Enter your timezone here in IANA format//",
"eventTypeId": <eventTypeId>
}
Variables (ask/collect in the conversation)
email
— The attendee’s email address and spell it back letter by letter to confirm; it must be a valid RFC-5322 email (e.g., alice@example.com) with no placeholders, spaces, or typos. Reject examples like: 'joe at gmail', 'test@test', or 'name@ domain.com'.(spell back to confirm)
name
— attendee full name
phoneNumber
— Attendee’s phone number must be in E.164 format. Include the leading '+' and country code; remove all spaces, parentheses, and dashes.
Examples — US: +12135551234, India: +919876543210.
start
— Booking start time in UTC ISO-8601 with a trailing Z. ISO-8601 with timezone or UTC Z
(e.g., 2025-09-01T10:00:00-07:00
)
timeZone
— IANA tz (e.g., America/Los_Angeles
)
eventTypeId
— integer (your Cal.com event type)
Messages (optional)
Start: “Great! I’ll go ahead and book your appointment now…”
Delay (7s): “Booking is taking a little longer than expected, please hold on…”
Failure: “Sorry, I couldn’t book your appointment. There was an error. Please try again.”
Tip: Initialize/test the action once the Zap is ready so Aigent360 discovers these fields.
2.1 Webhooks by Zapier (Catch Hook)
Create a new Zap → Trigger: Webhooks by Zapier → Catch Hook.
Copy the Webhook URL and paste it into the Aigent360 action endpoint.
From Aigent360, send a test payload so Zapier captures sample data (name, email, phoneNumber, start, timeZone, eventTypeId).
2.2 Code by Zapier (Run JavaScript)
Input Data mapping (from the Webhook sample)
attendeeName
⇐ name
attendeeEmail
⇐ email
attendeePhone
⇐ phoneNumber
start
⇐ start
timeZone
⇐ timeZone
eventTypeId
⇐ eventTypeId
Environment/Secrets (in Zapier)
CAL_API_KEY
— stored in Zapier Secrets/Vault (not in Aigent360).
JavaScript (paste into the Code step)
// Code by Zapier — JavaScript (returns `output`)
// TEMPLATE VERSION — fill inputs via Webhook; keep secrets in Zapier
// ===== Expected Input Data (from Webhooks by Zapier) =====
// name, email, phoneNumber, start, timeZone, eventTypeId
// (Also supports attendeeName/attendeeEmail/attendeePhone aliases)
// ===== Config / Secrets (Zapier) =====
// In Zap editor > Code step > "Environment Variables"/"Secrets":
// CAL_API_KEY=your_cal_dot_com_api_key
// DEFAULT_EVENT_TYPE_ID=3010028 // optional fallback
const API_URL = "https://api.cal.com/v2/bookings";
const CAL_API_VERSION = "2024-08-13";
const TOKEN = `Bearer ${process.env.CAL_API_KEY}`;
// ===== Inputs =====
const attendeeEmail = (inputData.attendeeEmail || inputData.email || "").toLowerCase();
const attendeeName = inputData.attendeeName || inputData.name || "";
const attendeePhone = inputData.attendeePhone || inputData.phoneNumber || "";
const startRaw = inputData.start || ""; // ISO with tz or UTC Z
const timeZone = inputData.timeZone || ""; // IANA tz, e.g. America/Los_Angeles
const eventTypeId = Number(inputData.eventTypeId || process.env.DEFAULT_EVENT_TYPE_ID || 0);
// ---- Helpers ----
const isRFC5322Email = (e) =>
/^(?:[a-zA-Z0-9_'^&\-+])+(?:\.(?:[a-zA-Z0-9_'^&\-+])+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(e);
function toUTCISOString(isoLike) {
const d = new Date(isoLike);
if (isNaN(d)) throw new Error("Invalid `start`. Use ISO-8601 with timezone or Z (e.g. 2025-09-01T10:00:00-07:00).");
return d.toISOString(); // normalize to UTC 'Z'
}
// ---- Validate inputs ----
const errors = [];
if (!attendeeName) errors.push("Missing attendee name");
if (!isRFC5322Email(attendeeEmail)) errors.push("Invalid or missing email");
if (!startRaw) errors.push("Missing start");
if (!timeZone) errors.push("Missing timeZone (IANA)");
if (!eventTypeId) errors.push("Missing eventTypeId");
if (!process.env.CAL_API_KEY) errors.push("Server misconfig: CAL_API_KEY is not set in Zapier");
if (errors.length) throw new Error(`Validation error: ${errors.join(", ")}`);
const startUTC = toUTCISOString(startRaw);
// ---- Build Cal.com request body ----
const body = {
start: startUTC,
attendee: {
name: attendeeName,
email: attendeeEmail,
timeZone: timeZone,
phoneNumber: attendeePhone || undefined,
},
eventTypeId: eventTypeId, // fixed-length type → no lengthInMinutes required
};
// ---- Call Cal.com ----
const res = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": TOKEN,
"cal-api-version": CAL_API_VERSION,
},
body: JSON.stringify(body),
});
const raw = await res.text();
let data; try { data = JSON.parse(raw); } catch { data = { raw }; }
// ---- Error handling with helpful hints ----
if (!res.ok) {
const err = {
status: res.status,
statusText: res.statusText,
details: data,
hint:
res.status === 401 ? "Check CAL_API_KEY (Bearer <key>)"
: res.status === 422 ? "Verify eventTypeId/start/attendee fields"
: res.status === 409 ? "Potential conflict/double-booking"
: "See error details",
};
throw new Error(`Cal.com API error: ${JSON.stringify(err)}`);
}
// ---- Success payload back to the Zap (use in Aigent360) ----
output = {
ok: true,
booking_id: data?.data?.id || "",
booking_uid: data?.data?.uid || "",
meeting_url: data?.data?.meetingUrl || "",
start_utc: data?.data?.start || startUTC,
duration: data?.data?.duration || "",
response: data, // keep for logs/Action Results mapping
};
Error handling in Zapier
In the Zapier Canvas, the Code step can have Success and Error branches.
Success branch: proceed to next actions (e.g., send confirmation, write to a sheet/CRM).
Error branch: when the Code step throws an error (validation/API), log details (e.g., to Google Sheets/Slack/Email) and optionally notify your team.
3) Validation & best practices
Keep secrets (Cal.com key) in Zapier, not in Aigent360.
Confirm email by spelling it back; normalize phone to E.164 before sending.
Always include IANA time zone and convert start
to UTC Z in Code.
Use descriptive error messages so the Error branch has useful context.
4) Example payloads
Aigent360 → Zapier (Catch Hook body)
{
"name": "Jane Roe",
"email": "jane.roe@example.com",
"phoneNumber": "+12135551234",
"start": "2025-09-01T10:00:00-07:00",
"timeZone": "America/Los_Angeles",
"eventTypeId": 3010028
}
Zapier Code → Cal.com POST
{
"start": "2025-09-01T17:00:00Z",
"attendee": {
"name": "Jane Roe",
"email": "jane.roe@example.com",
"timeZone": "America/Los_Angeles",
"phoneNumber": "+12135551234"
},
"eventTypeId": 3010028
}
Zapier success output (to Aigent360)
{
"ok": true,
"booking_id": 123456,
"booking_uid": "abc123",
"meeting_url": "https://cal.com/...",
"start_utc": "2025-09-01T17:00:00Z",
"duration": 15
}
5) Set up screenshots for reference
Aigent360: booking_appointment
action → Connect to API (Webhook URL), Configure (Raw body + variables), Messages, Action Results.
Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
Slide 6
Zapier : Webhooks (Catch Hook) → Code by Zapier with Success and Error branches (e.g., log to Google Sheets or notify via Slack or to any CRM).
Whole Workflow image