Customer Support

Customer Support Scheduling

This guide shows how to build an AI support agent that handles most tickets autonomously but escalates complex issues — billing disputes, technical troubleshooting, account recovery — to a human callback.

The problem

Your AI support agent resolves 80% of tickets. But the other 20% need a human. Today, the agent says “a team member will reach out” and the customer waits. Sometimes for hours. Sometimes they never hear back.

With Slotflow, the agent books a specific callback time while the customer is still engaged. The customer knows exactly when they’ll get help. No uncertainty, no ghosting.

Architecture

Customer contacts support → AI Agent triages
→ Simple issue → Agent resolves autonomously
→ Complex issue → Agent finds available support rep
→ Offers callback times to customer
→ Books callback via Slotflow
→ Webhook → Support system updated

Setup

1. Create your support team

1const supportTeam = [
2 { name: "Jordan Lee", role: "senior_support", timezone: "America/Los_Angeles" },
3 { name: "Priya Patel", role: "billing_specialist", timezone: "America/New_York" },
4 { name: "Tom Mueller", role: "technical_support", timezone: "Europe/Berlin" },
5];
6
7const humanIds = {};
8for (const agent of supportTeam) {
9 const res = await fetch("https://api.slotflow.dev/v1/humans", {
10 method: "POST",
11 headers: {
12 "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}`,
13 "Content-Type": "application/json",
14 },
15 body: JSON.stringify(agent),
16 });
17 const human = await res.json();
18 humanIds[human.role] = human.id;
19}

2. Set staggered availability

Support teams often have overlapping shifts for coverage:

1// West coast: 8am-4pm PT
2await setAvailability(humanIds.senior_support, {
3 working_days: [1, 2, 3, 4, 5],
4 work_start: "08:00",
5 work_end: "16:00",
6 meeting_durations: [15, 30], // Quick callbacks and longer sessions
7});
8
9// East coast: 9am-5pm ET (overlaps with west coast)
10await setAvailability(humanIds.billing_specialist, {
11 working_days: [1, 2, 3, 4, 5],
12 work_start: "09:00",
13 work_end: "17:00",
14 meeting_durations: [15, 30],
15});
16
17// Berlin: 9am-5pm CET (covers early morning US)
18await setAvailability(humanIds.technical_support, {
19 working_days: [1, 2, 3, 4, 5],
20 work_start: "09:00",
21 work_end: "17:00",
22 meeting_durations: [30, 60], // Technical issues need more time
23});

Agent escalation flow

The core function your agent calls when escalating a ticket:

1async function escalateToHuman({ ticketId, customerId, customerName, customerEmail, issueType, priority, summary }) {
2 const API_KEY = process.env.SLOTFLOW_API_KEY;
3 const BASE = "https://api.slotflow.dev/v1";
4 const headers = {
5 "Authorization": `Bearer ${API_KEY}`,
6 "Content-Type": "application/json",
7 };
8
9 // Route to the right specialist based on issue type
10 const repId = routeToSpecialist(issueType);
11 const duration = priority === "high" ? 30 : 15;
12
13 // Find available callback slots in the next 48 hours
14 const now = new Date();
15 const twoDaysOut = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);
16
17 const slotsRes = await fetch(
18 `${BASE}/humans/${repId}/slots?date_from=${formatDate(now)}&date_to=${formatDate(twoDaysOut)}&duration=${duration}`,
19 { headers }
20 );
21 const { slots, timezone } = await slotsRes.json();
22
23 if (slots.length === 0) {
24 // Fallback: try other specialists
25 return await tryOtherSpecialists(issueType, duration, headers);
26 }
27
28 // Offer the first 3 available slots to the customer
29 const options = slots.slice(0, 3);
30
31 return {
32 availableSlots: options,
33 timezone,
34 repId,
35 duration,
36 metadata: {
37 ticket_id: ticketId,
38 customer_id: customerId,
39 issue_type: issueType,
40 priority,
41 summary,
42 escalated_at: new Date().toISOString(),
43 },
44 };
45}
46
47function routeToSpecialist(issueType) {
48 const routing = {
49 billing: process.env.BILLING_SPECIALIST_ID,
50 technical: process.env.TECHNICAL_SUPPORT_ID,
51 account: process.env.SENIOR_SUPPORT_ID,
52 general: process.env.SENIOR_SUPPORT_ID,
53 };
54 return routing[issueType] || routing.general;
55}

Booking the selected slot

After the customer picks a time:

1async function bookCallback({ repId, selectedSlot, duration, customerName, customerEmail, metadata }) {
2 const res = await fetch("https://api.slotflow.dev/v1/bookings", {
3 method: "POST",
4 headers: {
5 "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}`,
6 "Content-Type": "application/json",
7 },
8 body: JSON.stringify({
9 human_id: repId,
10 starts_at: selectedSlot.starts_at,
11 duration,
12 attendee_name: customerName,
13 attendee_email: customerEmail,
14 metadata,
15 }),
16 });
17
18 if (res.status === 409) {
19 // Slot taken — offer fresh slots
20 return { success: false, reason: "slot_taken" };
21 }
22
23 const booking = await res.json();
24 return { success: true, booking };
25}

Webhook handler for support system

1app.post("/webhooks/slotflow", (req, res) => {
2 const { event, data } = req.body;
3
4 if (event === "booking.confirmed") {
5 // Update the support ticket
6 supportSystem.updateTicket(data.metadata.ticket_id, {
7 status: "callback_scheduled",
8 callback_time: data.starts_at,
9 callback_agent: data.human_id,
10 booking_id: data.id,
11 });
12
13 // Notify the support rep
14 notifyRep(data.human_id, {
15 customer: data.attendee_name,
16 time: data.starts_at,
17 issue: data.metadata.summary,
18 priority: data.metadata.priority,
19 });
20 }
21
22 if (event === "booking.cancelled") {
23 supportSystem.updateTicket(data.metadata.ticket_id, {
24 status: "callback_cancelled",
25 });
26 }
27
28 res.sendStatus(200);
29});

Cross-timezone handling

When your support reps span multiple timezones, Slotflow handles it automatically. Each human has their own timezone — the slot engine computes availability in their local time and returns slots in UTC.

Your agent just needs to present times in the customer’s preferred format:

1function formatSlotForCustomer(slot, customerTimezone) {
2 const start = new Date(slot.starts_at);
3 return start.toLocaleString("en-US", {
4 timeZone: customerTimezone,
5 weekday: "long",
6 month: "short",
7 day: "numeric",
8 hour: "numeric",
9 minute: "2-digit",
10 });
11 // "Monday, Mar 16, 10:00 AM"
12}

Cancellation flow

If a customer needs to cancel, your agent can free up the slot:

1async function cancelCallback(bookingId) {
2 const res = await fetch(`https://api.slotflow.dev/v1/bookings/${bookingId}`, {
3 method: "DELETE",
4 headers: { "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}` },
5 });
6 return await res.json(); // { id: "...", status: "cancelled" }
7}

The slot becomes immediately available for other customers.

Key takeaways

  • Route by issue type — billing issues to billing specialists, technical issues to engineers
  • Use short durations — 15-minute callbacks for quick issues, 30-minute for complex ones
  • Pass ticket context in metadata — your webhook handler has everything it needs to update the support system
  • Offer multiple time slots — let the customer choose from 2-3 options
  • Handle slot conflicts — re-query slots if the customer’s chosen time was already booked