Voice Agents

A voice agent that answers phone calls, checks your availability, and books meetings for you — 24/7. Powered by ElevenLabs Conversational AI and Twilio Voice.

Overview

Voice Agents add a conversational AI assistant to your LinkTime account. The assistant can:

  • Answer inbound phone calls and greet callers by name
  • List your available meeting types
  • Check real-time availability on your calendar
  • Book meetings, create contacts, and send confirmations
  • Make outbound calls for reminders, follow-ups, and confirmations (via Workflows)
  • Work via a browser-based web widget (free tier) or a dedicated phone number in 14 countries (Pro+)

Multi-Agent

As of v2.31.0, you can create multiple voice agents, each with its own personality, phone number, and configuration. This is ideal for businesses that need separate agents for different locations, departments, or languages.

Agent limits by plan

PlanMax Agents
Free1
Pro2
Business20

Shared minutes pool: Your monthly voice minutes are shared across all agents. If you have 60 minutes on the Pro plan, those 60 minutes are consumed by calls to any of your agents.

Per-agent configuration: Each agent can have its own name, voice, greeting, business context, language, linked event types, and active hours schedule. This lets you tailor each agent to a specific role or audience.

Active Hours

Set business hours for each agent so it only answers calls during the times you choose. Outside of active hours, callers hear an after-hours response instead.

How it works

  • 7-day schedule: Configure start and end times for each day of the week (e.g., Mon-Fri 9 AM - 6 PM, Sat 10 AM - 2 PM, Sun off).
  • Timezone-aware: Active hours are evaluated in the agent's configured timezone, so your schedule works correctly regardless of the caller's location.
  • After-hours behavior: Choose what happens outside active hours: play a voicemail greeting, deliver a custom message, or redirect the caller to your online booking page.

Default (24/7): If no active hours are configured, the agent operates around the clock. You can enable active hours at any time from the agent settings page.

Linked Event Types

Control which of your event types each agent is allowed to book. This gives you fine-grained control over what each agent offers to callers.

  • Default: all event types. When no specific event types are linked, the agent can book any of your active, non-secret event types.
  • Restricted: link specific event types to limit what the agent offers. For example, a "Sales" agent might only book "Discovery Call" and "Demo", while a "Support" agent only books "Technical Consultation".

Multi-location tip: Combine linked event types with multi-agent to create location-specific booking flows. Each location's agent only shows the event types available at that site.

Use Cases

Here are three common patterns for multi-agent setups.

Multi-Location Business

A clinic, salon, or service business with multiple physical locations. Each location gets its own agent with a dedicated phone number.

  • -Agent "Downtown Office" answers calls for +1 (212) 555-0100, books only downtown event types, open Mon-Fri 8 AM - 6 PM.
  • -Agent "Midtown Office" answers calls for +1 (212) 555-0200, books only midtown event types, open Mon-Sat 9 AM - 7 PM.

Sales + Support Split

Route inbound calls to the right AI agent depending on the caller's intent.

  • -Agent "Sales" qualifies leads, books "Discovery Call" and "Product Demo" event types.
  • -Agent "Support" answers technical questions and books "Support Session" and "Troubleshooting Call" event types.

Multilingual Service

Serve callers in their preferred language with agents configured for different languages, all sharing the same pool of event types.

  • -Agent "English" (language: en) — primary number, answers in English.
  • -Agent "Espanol" (language: es) — separate number, answers in Spanish.
  • -Both agents can book the same event types. Minutes are deducted from the same shared pool.

Architecture

LinkTime uses ElevenLabs Conversational AI for speech-to-text, LLM reasoning, and text-to-speech. Twilio provides phone number provisioning and telephony. ElevenLabs natively bridges the two — no custom TwiML or WebSocket code needed.

Inbound Call Flow

Caller dials your Twilio number

→ Twilio routes to ElevenLabs (auto-configured)

→ ElevenLabs calls /api/voice/webhook/personalization for greeting + context

→ Agent converses with caller, calling tool webhooks as needed:

/api/voice/tools/event-types (list meeting types)

/api/voice/tools/available-slots (check calendar)

/api/voice/tools/create-booking (book meeting)

→ Call ends → ElevenLabs calls /api/voice/webhook/call-status with transcript + duration

Outbound Call Flow

Workflow trigger (e.g., 1h before meeting)

→ LinkTime calls ElevenLabs outbound API

→ Twilio dials the callee

→ Agent converses (reminder, follow-up, etc.)

→ Post-call webhook updates CallLog

Web Widget Flow (free tier)

Visitor clicks "Talk to AI Assistant" on your booking page

→ Browser mic → WebRTC → ElevenLabs

→ Same agent, same tools, same booking flow

→ Zero Twilio cost (no phone number needed)

Plan Limits

PlanMonthly MinutesPhone NumberWeb Widget
Free5 minutesNoYes
Pro60 minutesYes (dedicated number)Yes
OrganizationUnlimitedYes (shared org number)Yes

How minute enforcement works: Before each inbound call, the personalization webhook checks remaining minutes. If the user has <1 minute left, the agent greets the caller with an "unavailable" message and directs them to the online booking URL. Calls are never cut off mid-conversation — small overages are allowed. After each call, if the user exceeds their limit, the agent is auto-deactivated until the next billing period.

Setup Guide

  1. 1

    Navigate to Voice Agents

    Go to Dashboard → Voice Agents in your LinkTime dashboard.

  2. 2

    Choose a template

    This triggers a multi-step provisioning process: (1) creates an ElevenLabs conversational agent with your booking tools, (2) purchases a phone number from Twilio in your selected country (Pro+ only), (3) imports the number into ElevenLabs so it auto-answers calls. Takes about 5 seconds.

  3. 3

    Your agent is live

    You'll see your agent status (Active), phone number, and usage bar. Callers can now call the number and the AI will answer, check your calendar, and book meetings.

  4. 4

    Customize (optional)

    Go to Voice Agents → Settings to change the agent name, custom greeting, voice, business context, and language.

Agent API (CRUD)

Manage your voice agents programmatically. All endpoints require Clerk session authentication.

v2.31.0 — Multi-agent endpoints: The new plural endpoints (/api/voice/agents) support creating and managing multiple agents. The legacy singular endpoint (/api/voice/agent) continues to work for backwards compatibility and operates on your first (default) agent.

GET/api/voice/agents

Returns all voice agents for the current user, along with shared usage stats and plan info.

Response Example

{
  "agents": [
    {
      "id": "cm_abc...",
      "agentName": "Sales Assistant",
      "voiceId": "21m00Tcm4TlvDq8ikWAM",
      "greeting": null,
      "businessContext": "We sell enterprise SaaS.",
      "language": "en",
      "phoneNumber": "+19143593794",
      "isActive": true,
      "elevenlabsAgentId": "abc123...",
      "createdAt": "2026-02-08T15:00:00.000Z"
    },
    {
      "id": "cm_def...",
      "agentName": "Soporte Tecnico",
      "language": "es",
      "phoneNumber": "+19143593800",
      "isActive": true,
      "elevenlabsAgentId": "def456...",
      "createdAt": "2026-02-10T10:00:00.000Z"
    }
  ],
  "usage": {
    "minutesUsed": 12,
    "minuteLimit": 60,
    "remaining": 48
  },
  "plan": {
    "name": "PRO",
    "hasPhoneNumber": true,
    "maxMinutes": 60,
    "maxAgents": 2
  }
}
POST/api/voice/agents

Create a new voice agent. Provisions an ElevenLabs conversational agent and (for Pro+ users) a dedicated Twilio phone number. Returns 403 if the user has reached their plan's agent limit.

Request Body

FieldTypeDefaultDescription
agentNamestring"Scheduling Assistant"Name used in greetings (1-100 chars)
greetingstring?Auto-generatedCustom first message (max 500 chars)
businessContextstring?nullDescribe your business so the agent can answer questions (max 5000 chars)
voiceIdstring?Rachel (default)ElevenLabs voice ID
languagestring?"en"Agent language code

Example (curl)

curl -X POST https://linktime.io/api/voice/agents \
  -H "Content-Type: application/json" \
  -H "Cookie: __session=<clerk_session>" \
  -d '{
    "agentName": "Sarah",
    "greeting": "Hi! I'm Sarah, Emmanuel's scheduling assistant.",
    "businessContext": "I run a design consultancy specializing in UX.",
    "language": "en"
  }'

Example (JavaScript)

const response = await fetch("/api/voice/agents", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    agentName: "Sarah",
    greeting: "Hi! I'm Sarah, Emmanuel's scheduling assistant.",
    businessContext: "I run a design consultancy specializing in UX.",
    language: "en",
  }),
});
const { agent } = await response.json();
console.log(agent.phoneNumber); // "+19143593794"

Provisioning rollback: If any step fails (agent creation, number purchase, or ElevenLabs import), all previous steps are rolled back automatically. You won't be charged for a Twilio number if the ElevenLabs import fails.

GETPATCHDELETE/api/voice/agents/[id]

Read, update, or delete a specific agent by its ID.

  • GET — Returns the agent's full configuration, including linked event types and active hours.
  • PATCH — Update agent settings (name, voice, greeting, businessContext, language). Changes sync to ElevenLabs automatically.
  • DELETE — Deactivate the agent. Its phone number enters a 30-day hold period before release.

Example (update agent)

curl -X PATCH https://linktime.io/api/voice/agents/cm_abc... \
  -H "Content-Type: application/json" \
  -H "Cookie: __session=<clerk_session>" \
  -d '{
    "agentName": "Alex",
    "businessContext": "We are a law firm specializing in IP law."
  }'
PUT/api/voice/agents/[id]/event-types

Link specific event types to this agent. Send an array of event type IDs. Send an empty array to reset to "all event types" (the default).

Request Body

{
  "eventTypeIds": ["cm_evt1...", "cm_evt2..."]
}
PATCH/api/voice/agents/[id]/active-hours

Configure the agent's active hours schedule. Pass enabled: false to disable active hours (agent operates 24/7).

Request Body

{
  "enabled": true,
  "timezone": "America/New_York",
  "afterHoursBehavior": "voicemail",
  "schedule": [
    { "day": "monday",    "start": "09:00", "end": "18:00" },
    { "day": "tuesday",   "start": "09:00", "end": "18:00" },
    { "day": "wednesday", "start": "09:00", "end": "18:00" },
    { "day": "thursday",  "start": "09:00", "end": "18:00" },
    { "day": "friday",    "start": "09:00", "end": "17:00" },
    { "day": "saturday",  "start": "10:00", "end": "14:00" }
  ]
}
POSTDELETE/api/voice/agents/[id]/phone

Manage phone numbers for a specific agent.

  • POST — Provision a new Twilio phone number and bind it to this agent (Pro+ plan required).
  • DELETE — Release the agent's phone number. The number enters a 30-day hold before being recycled.

Usage API

GET/api/voice/usage

Returns voice calling usage for the current billing period. Requires Clerk session auth.

Response Example

{
  "minutesUsed": 12,
  "minuteLimit": 60,
  "remaining": 48,
  "callsThisMonth": 8,
  "avgDurationSeconds": 90,
  "topOutcomes": [
    { "outcome": "booking_created", "count": 5 },
    { "outcome": "info_provided", "count": 2 },
    { "outcome": "no_action", "count": 1 }
  ]
}

Tool Webhooks (mid-call)

These endpoints are called by the ElevenLabs agent during a live conversation to access your calendar and booking system. They are authenticated with a shared secret in the X-Voice-Secret header.

Note: You don't call these endpoints directly. They are configured automatically when your agent is created and are invoked by ElevenLabs during phone/web conversations.

POST/api/voice/tools/event-typesTool: list_event_types

Returns the user's active, non-secret event types. For organization agents, returns all members' event types with member names.

Request

{
  "user_id": "usr_abc123"
}

Response

{
  "event_types": [
    {
      "slug": "30-min",
      "name": "30 Minute Meeting",
      "duration_minutes": 30,
      "description": "Quick chat"
    },
    {
      "slug": "consultation",
      "name": "Consultation",
      "duration_minutes": 60,
      "description": "In-depth session"
    }
  ]
}
POST/api/voice/tools/available-slotsTool: get_available_slots

Checks real-time calendar availability. Wraps the core availability engine — accounts for existing bookings, calendar events, availability rules, and overrides.

Request

{
  "user_id": "usr_abc123",
  "event_type_slug": "30-min",
  "date": "2026-02-10",
  "timezone": "America/New_York"
}

Response

{
  "slots": [
    {
      "time": "9:00 AM",
      "start_time": "2026-02-10T14:00:00Z"
    },
    {
      "time": "10:30 AM",
      "start_time": "2026-02-10T15:30:00Z"
    },
    {
      "time": "2:00 PM",
      "start_time": "2026-02-10T19:00:00Z"
    }
  ],
  "date": "2026-02-10"
}
POST/api/voice/tools/create-bookingTool: create_booking

Creates a confirmed booking, creates/updates the contact, and returns a confirmation message the agent speaks to the caller.

Request

{
  "user_id": "usr_abc123",
  "event_type_slug": "30-min",
  "invitee_name": "John Doe",
  "invitee_email": "[email protected]",
  "start_time": "2026-02-10T14:00:00Z",
  "timezone": "America/New_York",
  "notes": "Wants to discuss Q3 plan",
  "invitee_phone": "+14155551234"
}

Response

{
  "success": true,
  "booking": {
    "id": "cm...",
    "event_name": "30 Minute Meeting",
    "date": "Monday, February 10, 2026",
    "time": "9:00 AM EST",
    "confirmation_message": "Your 30 Minute Meeting with Emmanuel is booked for Monday, February 10 at 9:00 AM EST. A confirmation email is on its way to [email protected]."
  }
}
POST/api/voice/tools/booking-detailsTool: get_booking_details

Retrieves booking details for reminder and follow-up calls. Used by outbound workflow calls.

Request

{
  "booking_id": "cm..."
}

Response

{
  "booking": {
    "id": "cm...",
    "event_name": "30 Minute Meeting",
    "invitee_name": "John Doe",
    "invitee_email": "[email protected]",
    "date": "Monday, February 10, 2026",
    "time": "9:00 AM EST",
    "status": "CONFIRMED",
    "meeting_link": "https://meet.google.com/...",
    "notes": "Wants to discuss Q3 plan"
  }
}

Error responses

All tool endpoints return user-friendly error messages that the agent can speak naturally to the caller:

{
  "error": true,
  "message": "I'm having trouble accessing the calendar right now. Can I take your number and have someone call you back?"
}

Lifecycle Webhooks

These endpoints handle the start and end of each call. They are called by ElevenLabs automatically.

POST/api/voice/webhook/personalization

Called when an inbound call arrives. Looks up the voice agent by the called phone number, checks plan limits, creates a CallLog entry, and returns dynamic variables for the agent greeting.

ElevenLabs sends

{
  "from": "+14155551234",
  "to": "+19143593794",
  "conversation_id": "conv_abc...",
  "call_sid": "CA..."
}

LinkTime responds (normal)

{
  "type": "conversation_initiation_client_data",
  "dynamic_variables": {
    "host_name": "Emmanuel",
    "business_context": "I run a design consultancy",
    "booking_url": "https://linktime.io/emmanuel",
    "host_email": "[email protected]",
    "host_timezone": "America/New_York",
    "agent_name": "Scheduling Assistant"
  },
  "conversation_config_override": {
    "agent": {
      "first_message": "Hi! I'm Emmanuel's scheduling assistant. How can I help you today?"
    }
  }
}

LinkTime responds (over minute limit)

{
  "type": "conversation_initiation_client_data",
  "conversation_config_override": {
    "agent": {
      "first_message": "I'm sorry, Emmanuel's scheduling assistant is currently unavailable. Please visit linktime.io/emmanuel to book a meeting online.",
      "prompt": {
        "prompt": "Apologize that you're unavailable and direct the caller to the booking URL. End the call politely."
      }
    }
  }
}
POST/api/voice/webhook/call-status

Called when a call ends. Authenticated via HMAC-SHA256 signature in the elevenlabs-signature header.

Payload from ElevenLabs

{
  "conversation_id": "conv_abc...",
  "status": "completed",
  "transcript": "Agent: Hi! I'm Emmanuel's assistant...\nCaller: I'd like to book a meeting...",
  "duration_secs": 142,
  "tool_calls": [
    { "name": "list_event_types", "result": "..." },
    { "name": "get_available_slots", "result": "..." },
    { "name": "create_booking", "result": "..." }
  ]
}

What happens: Updates the CallLog with duration, transcript, and cost estimate ($0.10/min). Determines the call outcome from tool calls (booking_created, info_provided, no_action). If the user is now over their monthly limit, the agent is auto-deactivated.

Web Widget (free tier)

Free-tier users (and all users) get a browser-based voice widget on their public booking page. It uses @elevenlabs/react with WebRTC — no phone number or Twilio needed.

How it works

  1. 1.When your voice agent is active, a "Talk to AI Assistant" button appears on your booking page (linktime.io/your-username).
  2. 2.The visitor clicks the button → browser requests microphone permission.
  3. 3.A WebRTC connection is established directly with ElevenLabs. The AI agent greets the visitor and can check availability, book meetings, etc.
  4. 4.The visitor clicks "End Call" when done. A CallLog is created via the post-call webhook.

Embedding on external sites

The widget is built into your LinkTime booking page automatically. To add voice to an external site, use the Embed Widget which includes the voice button when your agent is active.

<!-- Embed your LinkTime booking page with voice widget -->
<iframe
  src="https://linktime.io/your-username?embed=true"
  width="100%"
  height="700"
  frameborder="0"
  style="border: none; border-radius: 12px;"
></iframe>

Workflow Integration (outbound calls)

The AI_CALL workflow action lets you trigger outbound voice agent calls based on booking events. This requires an active voice agent with a phone number (Pro+ plan).

Setting up a workflow

  1. 1.Go to Dashboard → Workflows and create a new workflow.
  2. 2.Choose a trigger (e.g., "Before Meeting" → 1 hour).
  3. 3.Add a "Voice Call" action.
  4. 4.Configure: who to call (invitee, host, or custom number) and call type (reminder, confirmation, follow-up, or custom).

Call types

TypeDescriptionExample trigger
reminderReminds the invitee of their upcoming meeting, offers to confirm or reschedule1 hour before meeting
confirmationConfirms the booking was made, reads back the detailsImmediately after booking
follow_upFollows up after a meeting, offers to schedule another1 day after meeting
customCustom message you defineAny trigger

AI_CALL action configuration

// Workflow action configuration (internal)
{
  "type": "AI_CALL",
  "config": {
    "to": "invitee",          // "invitee" | "host" | "custom"
    "customPhone": null,       // Required when to = "custom"
    "callType": "reminder",    // "reminder" | "follow_up" | "confirmation" | "custom"
    "customMessage": null      // Optional message for the agent
  }
}

Safety checks: Before each outbound call, LinkTime checks: (1) the target phone hasn't opted out via STOP keyword, (2) the user hasn't exceeded their monthly minute limit, (3) the user has an active voice agent with a phone number. If any check fails, the call is skipped and logged.

Security

Tool webhook authentication

All 4 tool endpoints verify the X-Voice-Secret header against ELEVENLABS_WEBHOOK_SECRET. This secret is configured in the ElevenLabs agent as a "Secret" and sent with every tool call.

Post-call webhook signature

The /api/voice/webhook/call-status endpoint verifies HMAC-SHA256 signatures using ELEVENLABS_SIGNING_SECRET and crypto.timingSafeEqual to prevent timing attacks.

User isolation

Tool webhooks include user_id or organization_id in the request body (set when the agent is created). Handlers only return that user's data — a user's agent can never access another user's calendar or bookings.

Outbound call restrictions

Outbound calls can only be made to numbers from confirmed bookings or explicitly provided in workflow configurations. The system checks the SmsOptOut table before every outbound call — if someone texts STOP to opt out of SMS, they're also excluded from AI calls.

TCPA compliance

The AI agent discloses it's an AI assistant in its greeting. Opt-out is checked before every outbound call. Inbound calls are always answered (caller initiated). The 30-day number hold ensures callers don't reach a disconnected number immediately after an agent is deactivated.

Troubleshooting

"Failed to create AI agent"

Check that the ELEVENLABS_API_KEY environment variable is set and valid. The ElevenLabs account must have Conversational AI access (Creator plan or higher).

"Failed to provision phone number"

Verify Twilio credentials (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN). The Twilio account must be upgraded from trial to paid for buying numbers. Check that phone number availability exists for the requested country.

Agent doesn't answer calls

Verify the agent is active in the dashboard. Check if the user has exceeded their monthly minute limit (the agent auto-deactivates). Ensure the phone number is still assigned — after deactivation, there's a 30-day hold before release.

Web widget shows "Microphone access required"

The browser must grant microphone permission for the WebRTC voice connection. On iOS Safari, microphone access must be allowed for the domain. Check browser settings → Site permissions → Microphone.

Agent says "having trouble with the scheduling system"

This means a tool webhook failed. Check server logs for errors on the /api/voice/tools/* routes. Common causes: calendar not connected (Google/Outlook token expired), database connection issue, or the ELEVENLABS_WEBHOOK_SECRET doesn't match.

Outbound workflow calls are skipped

Check: (1) the user has an active voice agent, (2) the user hasn't exceeded their minute limit, (3) the target phone number hasn't opted out (STOP), (4) the invitee has a phone number on the booking. All skip reasons are logged in the workflow execution log.

Environment Variables

VariableRequiredDescription
ELEVENLABS_API_KEYYesElevenLabs API key (get one here)
ELEVENLABS_WEBHOOK_SECRETYesShared secret for tool webhook auth (set in ElevenLabs agent config)
ELEVENLABS_SIGNING_SECRETYesHMAC secret for post-call webhook signature verification
TWILIO_ACCOUNT_SIDFor phoneTwilio Account SID (Twilio Console)
TWILIO_AUTH_TOKENFor phoneTwilio Auth Token

Need Help?

Our support team can help you set up and troubleshoot Voice Agents.