# LinkTime — Full Documentation > Complete documentation for LinkTime scheduling platform. > Last updated: February 2026 --- # Quickstart Guide > Go from sign-up to your first booking link in under 5 minutes. > Source: https://linktime.io/docs/getting-started/quickstart > Last updated: February 2026 ## 1. Create Your Account Sign up with your Google or Microsoft account. No credit card required -- the free plan includes every feature with generous limits. > **Tip:** Use the same account you use for your work calendar. This makes calendar integration automatic. ## 2. Connect Your Calendar Go to Settings -> Integrations and connect your Google Calendar or Outlook Calendar. LinkTime uses this to: - Check your availability (so you never get double-booked) - Create calendar events when bookings are confirmed - Include video meeting links in calendar invites ## 3. Set Your Availability Visit Availability to set your working hours. You can: - Set different hours for each day of the week - Add date-specific overrides (holidays, vacations) - Configure buffer time between meetings - Set minimum scheduling notice (e.g., at least 4 hours ahead) ## 4. Create an Event Type Go to Event Types and create your first event. Choose a name, duration, and location (Google Meet, Zoom, phone, etc.). > **Example:** "30 Minute Meeting" -- 30 min, Google Meet, available Mon-Fri 9am-5pm. ## 5. Share Your Link Your booking page is live at `linktime.io/yourusername`. Share it via: - Email signature - Social media profiles - Your website (use the [embed widget](https://linktime.io/docs/developers/embed)) - Direct message to someone you want to meet with ## What's Next? - **Add Zoom** -- Auto-create Zoom links for bookings. [Learn more](https://linktime.io/docs/video-conferencing/zoom) - **Set Up Webhooks** -- Get notified when bookings happen. [Learn more](https://linktime.io/docs/developers/webhooks) - **Embed on Your Site** -- Add booking widget to your website. [Learn more](https://linktime.io/docs/developers/embed) - **Connect Make.com** -- Automate with 1000+ apps. [Learn more](https://linktime.io/docs/automation/make) --- # Routing Forms > Source: https://linktime.io/docs/getting-started/routing-forms ## Overview Routing Forms are smart intake forms that ask visitors a few questions and automatically direct them to the right event type. Instead of showing your entire list of event types, a routing form guides visitors to exactly the meeting they need. ## How Routing Forms Work A visitor opens your routing form URL. They see a short form with questions you defined. Based on their answers, they're automatically redirected to the matching event type's booking page. It's like having a receptionist who asks the right questions and sends people to the right place. ## Field Types | Type | Description | |------|-------------| | Text | Free-form short text input | | Textarea | Longer free-form text | | Select | Dropdown with predefined options (most common for routing) | | Multi-select | Checkboxes, multiple options allowed | | Number | Numeric input | | Email | Email address with validation | | Phone | Phone number input | ## Routing Rules Rules determine which event type a visitor sees based on their answers: - Each rule says: "If the answer to field X matches Y, route to event type Z" - Rules are checked in order — the first match wins - You can set a default (fallback) event type for unmatched answers ### Operators | Operator | Example | |----------|---------| | Equals | "Industry" equals "Healthcare" | | Not Equals | "Company Size" not equals "Enterprise" | | Contains | "Message" contains "urgent" | | Greater Than | "Team Size" greater than 50 | | Less Than | "Budget" less than 1000 | ### Example ``` Rule 1: If "What brings you here?" equals "I want a demo" → Product Demo Rule 2: If "What brings you here?" equals "I need support" → Support Call Default: → General Consultation ``` ## Creating a Routing Form 1. Go to Dashboard → Routing Forms 2. Click "Create Routing Form" or choose a template 3. Name your form and add a description (visitors see this) 4. Add fields — the questions you want to ask 5. Set up routing rules 6. Save and share the form URL ## Templates LinkTime includes 6 ready-to-use templates: - **Sales Qualifier** — qualifies leads before booking a sales call - **Support Triage** — routes to the right support tier - **Industry Selector** — different meetings for different industries - **Company Size Router** — routes by company size - **Service Type** — routes by the type of service requested - **General Intake** — a simple all-purpose intake form ## Sharing Your Form Every routing form gets a unique URL: ``` linktime.io/yourname/route/form-slug ``` Share this URL instead of your booking page. You can also embed routing forms on your website using the [Embed Widget](https://linktime.io/docs/developers/embed). ## Plan Limits | Feature | Free | Pro | Business | |---------|------|-----|----------| | Routing forms | 1 | 5 | Unlimited | | Fields per form | 10 | 10 | 10 | | Routing rules | 10 | 10 | 10 | | Templates | All | All | All | | Custom slugs | Yes | Yes | Yes | ## Common Examples 1. **Route by meeting type** — Select field: "What type of meeting?" with options mapping to different event types 2. **Qualify leads** — Ask company size and budget, route enterprise leads to senior reps 3. **Support triage** — Ask "Is this urgent?" to route between same-day and next-week slots 4. **Multi-service business** — Photographer asks "What type of shoot?" to route to Wedding, Portrait, or Commercial ## Troubleshooting - **"No event types available"**: Check that routing rules point to published (not draft) event types. - **Rules not matching**: Rules check in order — put more specific rules first. Check operator type ("contains" vs "equals"). - **Form URL not working**: Make sure the form is published (not draft). Verify the username/slug in the URL. --- # HubSpot CRM Integration > Automatically sync contacts and log meetings in HubSpot when bookings are created through LinkTime. > Source: https://linktime.io/docs/crm/hubspot > Last updated: February 2026 ## Overview The HubSpot integration connects your LinkTime account to your HubSpot CRM via OAuth 2.0. When someone books time with you, LinkTime automatically creates or updates a contact in HubSpot and logs the meeting -- so your CRM stays up to date without any manual data entry. > **One-way sync:** Data flows from LinkTime to HubSpot only. Changes made directly in HubSpot (like editing a contact) are not synced back to LinkTime. ## Prerequisites - A HubSpot account (Free, Starter, Professional, or Enterprise) - Admin or Super Admin permissions in your HubSpot portal - A LinkTime Pro plan or higher ## Connecting HubSpot 1. **Go to Integrations** -- Navigate to Dashboard -> Integrations and find the **CRM** section. 2. **Click "Connect" on HubSpot** -- You'll be redirected to HubSpot's authorization page where you can choose which HubSpot portal to connect. 3. **Authorize LinkTime** -- Review the permissions and click "Grant access". LinkTime requests access to read and write contacts and read deals in your CRM -- nothing else. 4. **Done!** -- You'll be redirected back to LinkTime. HubSpot will show as "Connected" with your portal ID. All future bookings will automatically sync. ## What Gets Synced ### Contacts When someone books with you, LinkTime creates or updates a contact in HubSpot using their email address as the unique identifier (deduplication). **Fields synced:** - **Email** (used as dedup key) - **First name** and **last name** (split from the invitee's full name) - **Phone number** (if provided during booking) If a contact with that email already exists in HubSpot, the existing record is updated -- no duplicate contacts are created. ### Meetings A HubSpot meeting activity is created and associated with the contact. This appears on the contact's timeline in HubSpot. **Meeting details logged:** - **Title** (event type name + invitee name) - **Start and end times** - **Description** (invitee's notes, or "Booked via LinkTime") - **Outcome** (SCHEDULED, RESCHEDULED, or CANCELED) ## What Happens When... - **A booking is created** -- Contact is upserted (created or updated) in HubSpot. A meeting activity is created with outcome "SCHEDULED" and linked to the contact. - **A booking is rescheduled** -- The meeting times are updated in HubSpot and the outcome is changed to "RESCHEDULED". - **A booking is cancelled** -- The meeting outcome is updated to "CANCELED" in HubSpot. The meeting record is not deleted -- it stays on the contact's timeline for history. ## Reliability HubSpot sync runs in the background and never blocks the booking process. If HubSpot is temporarily unavailable or returns an error, your booking still completes normally -- the confirmation email is still sent, and the calendar event is still created. > **If a sync fails silently:** The booking still works. The contact or meeting may not appear in HubSpot for that particular booking. Future bookings will continue to sync normally. ## Disconnecting HubSpot To disconnect HubSpot from your LinkTime account: 1. Go to Dashboard -> Integrations 2. Find HubSpot under the CRM section 3. Click "Disconnect" Disconnecting removes the link between LinkTime and HubSpot. Contacts and meetings already created in HubSpot are not deleted -- they remain in your CRM. > **To fully revoke access:** You can also remove LinkTime from your HubSpot portal by going to **Settings -> Integrations -> Connected Apps** in HubSpot and removing LinkTime. ## Data & Privacy LinkTime takes your privacy seriously. Here's what we do and don't access: **What we access:** - Create and update contacts - Create and update meetings - Read contact data (for deduplication) - Your HubSpot portal ID **What we don't access:** - Deals or pipeline data - Email conversations - Marketing campaigns - Reporting or analytics - Other contacts not created by LinkTime Your HubSpot OAuth tokens are encrypted at rest using AES-256 encryption and automatically refreshed every 30 minutes. If your refresh token is ever revoked, the integration will gracefully disconnect and you can reconnect at any time. See the [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### HubSpot shows as "Not connected" after connecting This can happen if the OAuth token was revoked in HubSpot. Try disconnecting in LinkTime and reconnecting. Make sure you select the correct HubSpot portal during authorization. ### A booking was made but the contact didn't appear in HubSpot HubSpot sync is fire-and-forget -- if HubSpot was temporarily unavailable, the sync may have failed silently. Check that HubSpot is still connected in your Integrations page. Future bookings will continue to sync normally. ### I see duplicate contacts in HubSpot LinkTime uses email as the deduplication key. If contacts were created with different email addresses, they'll appear as separate records. You can merge them in HubSpot's contact management. ### I get a "permissions" error when connecting You need Admin or Super Admin access in your HubSpot portal to authorize third-party apps. Ask your HubSpot admin to either connect LinkTime themselves or grant you the necessary permissions. --- # Attio CRM Integration > Automatically sync contacts and log meetings in Attio when bookings are created through LinkTime. > Source: https://linktime.io/docs/crm/attio > Last updated: February 2026 ## Overview The Attio integration connects your LinkTime account to your Attio workspace via OAuth 2.0. When someone books time with you, LinkTime automatically creates or updates a person in Attio and adds a note to their timeline -- so your CRM stays up to date without manual data entry. > **One-way sync:** Data flows from LinkTime to Attio only. Changes made directly in Attio (like editing a person) are not synced back to LinkTime. ## Prerequisites - An Attio account (any plan) - Admin permissions in your Attio workspace - A LinkTime Pro plan or higher ## Connecting Attio 1. **Go to Integrations** -- Navigate to Dashboard -> Integrations and find the **CRM** section. 2. **Click "Connect" on Attio** -- You'll be redirected to Attio's authorization page where you can approve access for your workspace. 3. **Authorize LinkTime** -- Review the permissions and click "Allow access". LinkTime requests access to read and write people records and create notes -- nothing else. 4. **Done!** -- You'll be redirected back to LinkTime. Attio will show as "Connected" with your workspace name. All future bookings will automatically sync. ## What Gets Synced ### People (Contacts) When someone books with you, LinkTime creates or updates a person in Attio using their email address as the unique identifier (deduplication). **Fields synced:** - **Email** (used as dedup key) - **First name** and **last name** (split from the invitee's full name) - **Phone number** (if provided during booking) If a person with that email already exists in Attio, the existing record is updated -- no duplicate contacts are created. ### Notes (Timeline Entries) A note is created on the person's timeline in Attio for each booking event. This gives you a complete meeting history right in your CRM. **Note details logged:** - **Title** (event type name + invitee name) - **Start and end times** - **Description** (invitee's notes, or "Booked via LinkTime") ## What Happens When... - **A booking is created** -- A person is created or updated in Attio. A note is added to their timeline with the meeting title, times, and description. - **A booking is rescheduled** -- A new note titled "[RESCHEDULED]" is added to the person's timeline showing the new and previous times. - **A booking is cancelled** -- A new note titled "[CANCELLED]" is added to the person's timeline with the cancellation reason (if provided). ## Reliability Attio sync runs in the background and never blocks the booking process. If Attio is temporarily unavailable or returns an error, your booking still completes normally -- the confirmation email is still sent, and the calendar event is still created. > **If a sync fails silently:** The booking still works. The person or note may not appear in Attio for that particular booking. Future bookings will continue to sync normally. ## Token Security Attio uses long-lived OAuth access tokens -- there's no refresh token dance like some other integrations. Your access token is encrypted at rest using AES-256 encryption. If the token is ever revoked (e.g., you remove LinkTime from Attio settings), the integration will automatically disconnect and you can reconnect at any time. ## Disconnecting Attio To disconnect Attio from your LinkTime account: 1. Go to Dashboard -> Integrations 2. Find Attio under the CRM section 3. Click "Disconnect" Disconnecting removes the link between LinkTime and Attio. People and notes already created in Attio are not deleted -- they remain in your CRM. > **To fully revoke access:** You can also remove LinkTime from your Attio workspace by going to **Settings -> Developers -> Connected Apps** in Attio and removing LinkTime. ## Attio vs HubSpot vs Salesforce LinkTime supports Attio, HubSpot, and Salesforce as CRM integrations. You can connect one or more simultaneously. Here's how they compare: | Feature | Attio | HubSpot | Salesforce | |---------|-------|---------|------------| | Contact sync | People (assert upsert) | Contacts (batch upsert) | SOQL query + create/update | | Meeting logging | Notes on person timeline | Meeting object + association | Event on Activity Timeline | | Token lifecycle | Long-lived (no refresh) | 30-min expiry (auto-refresh) | ~2hr expiry (auto-refresh) | | Cancel/reschedule | New note with status prefix | Meeting outcome updated | [Cancelled] prefix on Event | | Best for | Startups, small teams | Mid-size, growth-stage | Enterprise, large teams | All three integrations can run simultaneously -- connecting one does not affect the others. See the [HubSpot](https://linktime.io/docs/crm/hubspot) and [Salesforce](https://linktime.io/docs/crm/salesforce) integration guides for more details. ## Data & Privacy LinkTime takes your privacy seriously. Here's what we do and don't access: **What we access:** - Create and update people - Create notes on timelines - Read people data (for deduplication) - Your workspace name and ID **What we don't access:** - Deals or pipeline data - Email conversations - Lists or segments - Reporting or analytics - Other records not created by LinkTime Your Attio OAuth token is encrypted at rest using AES-256 encryption. If the token is ever revoked, the integration will gracefully disconnect and you can reconnect at any time. See the [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### Attio shows as "Not connected" after connecting This can happen if the OAuth token was revoked in Attio. Try disconnecting in LinkTime and reconnecting. Make sure you have admin permissions in your Attio workspace. ### A booking was made but the person didn't appear in Attio Attio sync is fire-and-forget -- if Attio was temporarily unavailable, the sync may have failed silently. Check that Attio is still connected in your Integrations page. Future bookings will continue to sync normally. ### I see duplicate people in Attio LinkTime uses email as the deduplication key via Attio's assert endpoint. If people were created with different email addresses, they'll appear as separate records. You can merge them in Attio's people view by selecting both records. ### Notes are missing some details Notes contain the meeting title, start/end times, and the invitee's notes (if any). If the invitee didn't add notes during booking, the description defaults to "Booked via LinkTime". Video conferencing links are not included in the note. ### I get a "permissions" error when connecting You need admin access in your Attio workspace to authorize third-party apps. Ask your Attio workspace admin to either connect LinkTime themselves or grant you the necessary permissions. --- # Salesforce CRM Integration > Automatically sync contacts and log meeting events in Salesforce when bookings are created through LinkTime. > Source: https://linktime.io/docs/crm/salesforce > Last updated: February 2026 ## Overview The Salesforce integration connects your LinkTime account to your Salesforce org via OAuth 2.0. When someone books time with you, LinkTime automatically creates or updates a Contact in Salesforce and adds an Event to their Activity Timeline -- so your CRM stays up to date without manual data entry. > **One-way sync:** Data flows from LinkTime to Salesforce only. Changes made directly in Salesforce (like editing a Contact) are not synced back to LinkTime. ## Prerequisites - A Salesforce org (any edition with API access) - A user account with permission to create/edit Contacts and Events - A Connected App configured in Salesforce Setup (see below) - A LinkTime Pro plan or higher ## Connected App Setup Before connecting, your Salesforce admin needs to create a Connected App. This is a one-time setup: 1. **Open Salesforce Setup** -- Go to **Setup -> App Manager** (search "App Manager" in Quick Find). 2. **Create a New Connected App** -- Click **New Connected App**. Fill in the app name (e.g., "LinkTime") and a contact email. 3. **Enable OAuth Settings** -- Check **Enable OAuth Settings**. Set the callback URL to: `https://linktime.io/api/integrations/salesforce/callback` 4. **Select OAuth Scopes** -- Add these scopes: **Access the identity URL service (id)** and **Manage user data via APIs (api)**. The refresh token scope is automatic. 5. **Save and Copy Keys** -- After saving, copy the **Consumer Key** (Client ID) and **Consumer Secret** (Client Secret). You'll need these for the LinkTime configuration. > **Note:** It can take 2-10 minutes for a new Connected App to become active in Salesforce. If you get an "invalid_client_id" error immediately after setup, wait a few minutes and try again. ## Connecting Salesforce 1. **Go to Integrations** -- Navigate to Dashboard -> Integrations and find the **CRM** section. 2. **Click "Connect" on Salesforce** -- You'll be redirected to Salesforce's login page where you'll sign in and authorize LinkTime to access your org. 3. **Authorize LinkTime** -- Review the permissions and click "Allow". LinkTime requests access to create and update Contacts and Events via the Salesforce REST API. 4. **Done!** -- You'll be redirected back to LinkTime. Salesforce will show as "Connected" with your org name. All future bookings will automatically sync. ## Required Permissions The Salesforce user who connects LinkTime needs the following permissions in their Profile or Permission Set: - **Create** and **Edit** on Contact object - **Create** and **Edit** on Event object - **API Enabled** permission (usually enabled by default) - **SOQL query** access to Contact object (for deduplication) > **Tip:** Salesforce uses Profile-based permissions. The connected user's Profile determines what objects LinkTime can access -- not the OAuth scope alone. If syncing fails, ask your Salesforce admin to check the user's Profile permissions. ## What Gets Synced ### Contacts When someone books with you, LinkTime searches for an existing Contact by email via SOQL. If found, the Contact is updated; if not, a new one is created. **Fields synced:** - **Email** (used as dedup key via SOQL query) - **FirstName** and **LastName** (split from invitee's full name) - **Phone** (if provided during booking) If a Contact with that email already exists, only FirstName and Phone are updated -- LastName is preserved to avoid overwriting data your sales team has entered. ### Events (Calendar Items) An Event is created in Salesforce for each booking. Events appear on the Contact's Activity Timeline and in the Salesforce calendar. **Event details logged:** - **Subject** (event type name + invitee name) - **StartDateTime** and **EndDateTime** - **Description** (invitee's notes, or "Booked via LinkTime") - **WhoId** (linked to the Contact's Activity Timeline) ## What Happens When... - **A booking is created** -- A Contact is created or updated in Salesforce. An Event is added to their Activity Timeline with the meeting subject, times, and description. - **A booking is rescheduled** -- The existing Event in Salesforce is updated with the new start and end times. The description notes the previous time for reference. - **A booking is cancelled** -- The Event's Subject is prefixed with "[Cancelled]" to preserve the record on the timeline. The Event is not deleted, so your sales team retains the history. ## Reliability Salesforce sync runs in the background and never blocks the booking process. If Salesforce is temporarily unavailable or returns an error, your booking still completes normally -- the confirmation email is still sent, and the calendar event is still created. > **Token auto-refresh:** Salesforce access tokens expire every ~2 hours. LinkTime automatically refreshes them in the background with a 5-minute buffer -- you never need to reconnect manually. ## Sandbox Support LinkTime currently connects to **production Salesforce orgs** via `login.salesforce.com`. Sandbox orgs (using `test.salesforce.com`) are not yet supported but planned for a future release. > **Testing tip:** If you want to test the integration before going live, consider creating a Developer Edition org at **developer.salesforce.com** -- these are free production-type orgs with full API access. ## Disconnecting Salesforce To disconnect Salesforce from your LinkTime account: 1. Go to Dashboard -> Integrations 2. Find Salesforce under the CRM section 3. Click "Disconnect" Disconnecting removes the link between LinkTime and Salesforce. Contacts and Events already created in Salesforce are not deleted -- they remain in your CRM. > **To fully revoke access:** You can also remove LinkTime from your Salesforce org by going to **Setup -> Connected Apps OAuth Usage** and revoking access for LinkTime. ## Salesforce vs HubSpot vs Attio LinkTime supports all three CRM platforms. You can connect one or more simultaneously. Here's how they compare: | Feature | Salesforce | HubSpot | Attio | |---------|------------|---------|-------| | Contact sync | SOQL query + create/update | Contacts (batch upsert) | People (assert upsert) | | Meeting logging | Event on Activity Timeline | Meeting object + association | Notes on person timeline | | Token lifecycle | ~2hr expiry (auto-refresh) | 30-min expiry (auto-refresh) | Long-lived (no refresh) | | Cancel/reschedule | [Cancelled] prefix on Event | Meeting outcome updated | New note with status prefix | | Best for | Enterprise, large teams | Mid-size, growth-stage | Startups, small teams | All three integrations can run simultaneously. See the [HubSpot](https://linktime.io/docs/crm/hubspot) and [Attio](https://linktime.io/docs/crm/attio) integration guides for more details. ## Data & Privacy LinkTime takes your privacy seriously. Here's what we do and don't access: **What we access:** - Create and update Contacts - Create and update Events - Query Contacts by email (SOQL) - Read org name **What we don't access:** - Opportunities or pipeline data - Leads (Contacts only in v1) - Custom objects - Reports, dashboards, or analytics - Email, files, or attachments Your Salesforce OAuth tokens are encrypted at rest using AES-256 encryption. If tokens are revoked, the integration will gracefully disconnect and you can reconnect at any time. See the [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### FIELD_CUSTOM_VALIDATION_EXCEPTION Your Salesforce org has a validation rule that's blocking the Contact or Event creation. Check your validation rules in **Setup -> Object Manager -> Contact/Event -> Validation Rules** and ensure LinkTime's fields (Email, LastName, Subject, StartDateTime, EndDateTime) pass validation. ### INSUFFICIENT_ACCESS or INSUFFICIENT_PERMISSIONS The connected Salesforce user doesn't have permission to create/edit Contacts or Events. Ask your Salesforce admin to check the user's Profile and ensure Create/Edit permissions are granted on both objects. ### DUPLICATES_DETECTED Your Salesforce org has duplicate rules that are blocking Contact creation. You can either adjust the duplicate rule to "Alert" instead of "Block", or ensure the rule allows Contacts created via the API. ### Salesforce shows as "Not connected" unexpectedly This can happen if your Salesforce admin revoked the Connected App or if the refresh token was invalidated. Try disconnecting in LinkTime and reconnecting. If the issue persists, check that the Connected App is still active in Salesforce Setup. ### IP restrictions are blocking the connection If your org uses IP restrictions (Login IP Ranges or Trusted IP Ranges), ensure that LinkTime's server IPs are allowed. Alternatively, set the Connected App's **IP Relaxation** to "Relax IP restrictions" in the Connected App settings. --- # Zoom Integration > Automatically create Zoom meeting links for your LinkTime bookings. > Source: https://linktime.io/docs/video-conferencing/zoom > Last updated: February 2026 ## Overview The Zoom integration allows LinkTime to automatically create unique Zoom meeting links for each booking. When someone books time with you, they'll receive a calendar invite with a Zoom link - no manual setup required. > **What LinkTime accesses:** We only create meetings on your behalf. We do not access meeting recordings, participant data, or any meeting content. ## Adding the Zoom Integration 1. **Go to Integrations Settings** - Navigate to Settings -> Integrations in your LinkTime dashboard. 2. **Click "Connect" next to Zoom** - Find the Zoom section under Video Conferencing and click the Connect button. 3. **Authorize LinkTime** - You'll be redirected to Zoom's authorization page. Review the permissions and click "Allow" to grant LinkTime access to create meetings. 4. **Done!** - You'll be redirected back to LinkTime. Zoom will now show as "Connected" in your integrations. ## Using Zoom with Event Types Once connected, you can enable Zoom for any event type: 1. Go to Event Types in your dashboard 2. Edit an existing event type or create a new one 3. In the "Location" section, select "Zoom" 4. Save your changes Now, whenever someone books this event type, LinkTime will automatically create a Zoom meeting and include the join link in the confirmation email and calendar invite. ## What Happens When Someone Books - A unique Zoom meeting is created with the booking details - The Zoom link is added to the calendar event - Both you and your invitee receive the link in confirmation emails - Reminder emails also include the Zoom link ## Removing the Zoom Integration To disconnect Zoom from your LinkTime account: 1. Go to Settings -> Integrations 2. Find Zoom under Video Conferencing 3. Click "Disconnect" Note: Existing bookings with Zoom links will keep their meeting links. Only new bookings will be affected. > **To fully revoke access:** You can also remove LinkTime from your Zoom account by going to [Zoom Marketplace -> Manage -> Installed Apps](https://marketplace.zoom.us/user/installed) and removing LinkTime. ## Data & Privacy LinkTime takes your privacy seriously. Here's what we do and don't access: **What we access:** - Create meetings on your behalf - Your Zoom email (for account linking) **What we don't access:** - Meeting recordings - Participant information - Meeting chat or content - Your Zoom contacts Your Zoom OAuth tokens are encrypted at rest using AES-256 encryption. See our [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### Zoom shows as "Not connected" after connecting Try disconnecting and reconnecting. Make sure you're signed into the correct Zoom account when authorizing. If the issue persists, contact support@linktime.io. ### Zoom meeting wasn't created for a booking Check that the event type has "Zoom" selected as the location. Also verify your Zoom connection is still active in Settings -> Integrations. If Zoom is disconnected, reconnect it. ### I see "Application not found" error This can happen if there's a configuration issue. Please contact support@linktime.io with details about when the error occurred. --- # Google Meet Integration > Automatically create Google Meet links for your LinkTime bookings — no extra setup needed. > Source: https://linktime.io/docs/video-conferencing/google-meet > Last updated: February 2026 ## Overview Google Meet is built into the Google Calendar integration. Once you connect your Google Calendar, you can automatically generate unique Meet links for every booking — no separate app or account required. > **No separate OAuth:** Google Meet links are created through the Google Calendar API. If your Google Calendar is connected, Meet is ready to go. ## How It Works Unlike Zoom, Teams, or Webex, Google Meet doesn't require a separate OAuth connection. When LinkTime creates a Google Calendar event for a booking, it includes conference data that tells Google to auto-generate a Meet link. The link is extracted from the calendar event and sent to both parties. - Uses the Google Calendar API's built-in conferencing support - Each booking gets a unique Meet link — no link reuse - Works with any Google Workspace or free Gmail account ## Setup If you've already connected Google Calendar, you're done. If not: 1. **Go to Integrations** - Navigate to Settings -> Integrations in your dashboard. 2. **Connect Google Calendar** - Click "Connect" next to Google Calendar. Authorize LinkTime to access your calendar. 3. **Done!** - Google Meet is now available as a location option in your event types. ## Using with Event Types 1. Go to Event Types in your dashboard 2. Edit an existing event type or create a new one 3. In the "Location" section, select "Google Meet" 4. Save your changes ## What Happens on Booking - A Google Calendar event is created with an embedded Meet link - Both you and your invitee receive the Meet link in confirmation emails - Reminder emails include the Meet link - If Meet link creation fails, the booking still succeeds (link omitted) ## Data & Privacy **What we access:** - Create calendar events with Meet links - Read your calendar for availability checks **What we don't access:** - Meeting recordings or transcripts - Participant data or chat content - Your Google contacts or Drive Your Google OAuth tokens are encrypted at rest using AES-256 encryption. See our [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### Google Meet option is greyed out Make sure your Google Calendar is connected in Settings -> Integrations. Google Meet requires an active Google Calendar connection. ### Meet link wasn't included in the booking Verify the event type has "Google Meet" selected as the location. Also check that your Google Calendar connection is still active — token expiry can cause temporary disconnects, but LinkTime auto-refreshes tokens. ### I use Google Workspace — does it work? Yes. Google Meet works with both free Gmail accounts and Google Workspace (formerly G Suite). Your Workspace admin may need to ensure the Calendar API is enabled for your organization. --- # Microsoft Teams Integration > Automatically create Microsoft Teams meeting links for your LinkTime bookings. > Source: https://linktime.io/docs/video-conferencing/microsoft-teams > Last updated: February 2026 ## Overview The Microsoft Teams integration connects LinkTime to your Microsoft 365 account via the Microsoft Graph API. Once connected, LinkTime can automatically create unique Teams meeting links for each booking — no manual setup required per meeting. > **Requires Microsoft 365:** Teams meeting creation uses the Microsoft Graph OnlineMeetings API, which requires a Microsoft 365 subscription with Teams enabled. Free Microsoft accounts without Teams cannot generate meeting links. ## Adding the Microsoft Teams Integration 1. **Go to Integrations Settings** - Navigate to Settings -> Integrations in your LinkTime dashboard. 2. **Click "Connect" next to Microsoft Teams** - Find the Microsoft Teams section under Video Conferencing and click the Connect button. 3. **Authorize LinkTime** - You'll be redirected to Microsoft's authorization page. Review the permissions and click "Accept" to grant LinkTime access to create online meetings on your behalf. 4. **Done!** - You'll be redirected back to LinkTime. Microsoft Teams will now show as "Connected" in your integrations. ## Using Teams with Event Types Once connected, you can enable Microsoft Teams for any event type: 1. Go to Event Types in your dashboard 2. Edit an existing event type or create a new one 3. In the "Location" section, select "Microsoft Teams" 4. Save your changes Now, whenever someone books this event type, LinkTime will automatically create a Teams meeting and include the join link in the confirmation email and calendar invite. ## What Happens When Someone Books - A unique Teams meeting is created via the Microsoft Graph API - The Teams join link is added to the calendar event - Both you and your invitee receive the link in confirmation emails - Reminder emails also include the Teams link - If Teams link creation fails, the booking still succeeds (link omitted) ## Removing the Microsoft Teams Integration To disconnect Microsoft Teams from your LinkTime account: 1. Go to Settings -> Integrations 2. Find Microsoft Teams under Video Conferencing 3. Click "Disconnect" Note: Existing bookings with Teams links will keep their meeting links. Only new bookings will be affected. > **To fully revoke access:** You can also remove LinkTime from your Microsoft account by going to [myapps.microsoft.com](https://myapps.microsoft.com) and removing the LinkTime app permission. ## Data & Privacy **What we access:** - Create online meetings on your behalf - Your Microsoft email (for account linking) **What we don't access:** - Meeting recordings or transcripts - Participant information or chat - Your OneDrive, SharePoint, or email - Your Microsoft contacts Your Microsoft OAuth tokens are encrypted at rest using AES-256 encryption. See our [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### Teams shows as "Not connected" after connecting Try disconnecting and reconnecting. Make sure you're signed into the correct Microsoft account when authorizing. Your account must have a Microsoft 365 subscription with Teams enabled. ### Teams meeting wasn't created for a booking Check that the event type has "Microsoft Teams" selected as the location. Also verify your Teams connection is still active in Settings -> Integrations. If it's disconnected, reconnect it. ### I get a "permissions" or "admin consent" error Some organizations require an IT administrator to approve third-party apps. Ask your admin to grant consent for LinkTime in the [Microsoft Entra admin center](https://entra.microsoft.com) (formerly Azure AD). Once approved, try connecting again. ### I use a personal Microsoft account — does it work? Teams meeting creation requires Microsoft 365 with Teams. Personal accounts (outlook.com, hotmail.com) without a Teams license cannot generate meeting links. Consider using [Google Meet](https://linktime.io/docs/video-conferencing/google-meet) or [Zoom](https://linktime.io/docs/video-conferencing/zoom) as alternatives. --- # Webex Integration > Automatically create Cisco Webex meeting links for your LinkTime bookings. > Source: https://linktime.io/docs/video-conferencing/webex > Last updated: February 2026 ## Overview The Webex integration connects LinkTime to your Cisco Webex account. Once connected, LinkTime can automatically create unique Webex meeting links for each booking — your invitees join from their browser or the Webex app with no extra setup. > **What LinkTime accesses:** We only create meetings on your behalf. We do not access meeting recordings, participant data, or any meeting content. ## Adding the Webex Integration 1. **Go to Integrations Settings** - Navigate to Settings -> Integrations in your LinkTime dashboard. 2. **Click "Connect" next to Webex** - Find the Webex section under Video Conferencing and click the Connect button. 3. **Authorize LinkTime** - You'll be redirected to Webex's authorization page. Review the permissions and click "Accept" to grant LinkTime access to create meetings. 4. **Done!** - You'll be redirected back to LinkTime. Webex will now show as "Connected" in your integrations. ## Using Webex with Event Types Once connected, you can enable Webex for any event type: 1. Go to Event Types in your dashboard 2. Edit an existing event type or create a new one 3. In the "Location" section, select "Webex" 4. Save your changes Now, whenever someone books this event type, LinkTime will automatically create a Webex meeting and include the join link in the confirmation email and calendar invite. ## What Happens When Someone Books - A unique Webex meeting is created with the booking details - The Webex join link is added to the calendar event - Both you and your invitee receive the link in confirmation emails - Reminder emails also include the Webex link - If Webex link creation fails, the booking still succeeds (link omitted) ## Removing the Webex Integration To disconnect Webex from your LinkTime account: 1. Go to Settings -> Integrations 2. Find Webex under Video Conferencing 3. Click "Disconnect" Note: Existing bookings with Webex links will keep their meeting links. Only new bookings will be affected. > **To fully revoke access:** You can also remove LinkTime from your Webex account by going to [Webex Settings -> Integrations](https://settings.webex.com/integrations) and removing LinkTime. ## Data & Privacy **What we access:** - Create meetings on your behalf - Your Webex email (for account linking) **What we don't access:** - Meeting recordings or transcripts - Participant information - Meeting chat or content - Your Webex contacts or spaces Your Webex OAuth tokens are encrypted at rest using AES-256 encryption. See our [Privacy Policy](https://linktime.io/privacy) for more details. ## Troubleshooting ### Webex shows as "Not connected" after connecting Try disconnecting and reconnecting. Make sure you're signed into the correct Webex account when authorizing. If the issue persists, contact support@linktime.io. ### Webex meeting wasn't created for a booking Check that the event type has "Webex" selected as the location. Also verify your Webex connection is still active in Settings -> Integrations. If Webex is disconnected, reconnect it. ### My organization restricts third-party integrations Some Webex organizations require admin approval for third-party apps. Contact your Webex administrator and ask them to approve the LinkTime integration in the Webex Control Hub. Once approved, try connecting again. --- # 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. > Source: https://linktime.io/docs/ai/voice > Last updated: February 2026 ## 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 US phone number (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 | Plan | Max Agents | |------|------------| | Free | 1 | | Pro | 2 | | Business | 20 | **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](https://elevenlabs.io/docs/conversational-ai/overview) for speech-to-text, LLM reasoning, and text-to-speech. [Twilio](https://www.twilio.com) 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 | Plan | Monthly Minutes | Phone Number | Web Widget | |------|----------------|--------------|------------| | Free | 5 minutes | No | Yes | | Pro | 60 minutes | Yes (dedicated number) | Yes | | Organization | Unlimited | Yes (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. ## International Phone Provisioning (v2.45.0) Phone numbers can be provisioned in 14 countries. When creating a voice agent or adding a phone number, choose your preferred country from the country selector in the settings UI. Prices are approximate monthly costs charged by Twilio. | Country | Dial Code | Approx. Cost | SMS Inbound | |---------|-----------|-------------|-------------| | United States | +1 | ~$1/mo | Yes | | Canada | +1 | ~$1/mo | Yes | | United Kingdom | +44 | ~$1/mo | Yes | | France | +33 | ~$1/mo | Yes | | Germany | +49 | ~$1/mo | Yes | | Spain | +34 | ~$1/mo | Yes | | Netherlands | +31 | ~$1/mo | Yes | | Italy | +39 | ~$1/mo | Yes | | Australia | +61 | ~$2/mo | Yes | | Brazil | +55 | ~$3/mo | Yes | | Mexico | +52 | ~$2/mo | Yes | | Japan | +81 | ~$5/mo | Yes | | Singapore | +65 | ~$3/mo | Yes | | UAE | +971 | ~$5/mo | Yes | > **Country data source:** The full list with ISO codes, flags, and SMS capabilities is defined in `src/lib/countries.ts` (`PHONE_COUNTRIES` array). The `getCountryFlag()` helper maps E.164 phone numbers to country flag emojis for display. ### How country selection works 1. In the Voice Agents settings or Phone Numbers dashboard, a country dropdown shows all 14 supported countries with flag emojis. 2. When provisioning, LinkTime searches for a local number in the selected country first, then falls back to a toll-free number if no local numbers are available. 3. The provisioned number is automatically imported into ElevenLabs and bound to your agent. ## Setup Guide 1. **Navigate to Voice Agents** - Go to Dashboard -> Voice Agents in your LinkTime dashboard. 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. **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. **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:** ```json { "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:** | Field | Type | Default | Description | |-------|------|---------|-------------| | `agentName` | string | "Scheduling Assistant" | Name used in greetings (1-100 chars) | | `greeting` | string? | Auto-generated | Custom first message (max 500 chars) | | `businessContext` | string? | null | Describe your business so the agent can answer questions (max 5000 chars) | | `voiceId` | string? | Rachel (default) | ElevenLabs voice ID | | `language` | string? | "en" | Agent language code | **Example (curl):** ```bash curl -X POST https://linktime.io/api/voice/agents \ -H "Content-Type: application/json" \ -H "Cookie: __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):** ```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. ### GET / PATCH / DELETE /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):** ```bash curl -X PATCH https://linktime.io/api/voice/agents/cm_abc... \ -H "Content-Type: application/json" \ -H "Cookie: __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:** ```json { "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:** ```json { "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" } ] } ``` ### POST / DELETE /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:** ```json { "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-types **Tool:** list_event_types Returns the user's active, non-secret event types. For organization agents, returns all members' event types with member names. **Request:** ```json { "user_id": "usr_abc123" } ``` **Response:** ```json { "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-slots **Tool:** get_available_slots Checks real-time calendar availability. Wraps the core availability engine -- accounts for existing bookings, calendar events, availability rules, and overrides. **Request:** ```json { "user_id": "usr_abc123", "event_type_slug": "30-min", "date": "2026-02-10", "timezone": "America/New_York" } ``` **Response:** ```json { "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-booking **Tool:** create_booking Creates a confirmed booking, creates/updates the contact, and returns a confirmation message the agent speaks to the caller. **Request:** ```json { "user_id": "usr_abc123", "event_type_slug": "30-min", "invitee_name": "John Doe", "invitee_email": "john@example.com", "start_time": "2026-02-10T14:00:00Z", "timezone": "America/New_York", "notes": "Wants to discuss Q3 plan", "invitee_phone": "+14155551234" } ``` **Response:** ```json { "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 john@example.com." } } ``` ### POST /api/voice/tools/booking-details **Tool:** get_booking_details Retrieves booking details for reminder and follow-up calls. Used by outbound workflow calls. **Request:** ```json { "booking_id": "cm..." } ``` **Response:** ```json { "booking": { "id": "cm...", "event_name": "30 Minute Meeting", "invitee_name": "John Doe", "invitee_email": "john@example.com", "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: ```json { "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:** ```json { "from": "+14155551234", "to": "+19143593794", "conversation_id": "conv_abc...", "call_sid": "CA..." } ``` **LinkTime responds (normal):** ```json { "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": "emmanuel@example.com", "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):** ```json { "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:** ```json { "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](https://www.npmjs.com/package/@elevenlabs/react) with WebRTC -- no phone number or Twilio needed. ### How it works 1. When your voice agent is active, a "Talk to AI Assistant" button appears on your booking page (`linktime.io/your-username`). 2. The visitor clicks the button -> browser requests microphone permission. 3. A WebRTC connection is established directly with ElevenLabs. The AI agent greets the visitor and can check availability, book meetings, etc. 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](https://linktime.io/docs/developers/embed) which includes the voice button when your agent is active. ```html ``` ## 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. Go to Dashboard -> Workflows and create a new workflow. 2. Choose a trigger (e.g., "Before Meeting" -> 1 hour). 3. Add a "Voice Call" action. 4. Configure: who to call (invitee, host, or custom number) and call type (reminder, confirmation, follow-up, or custom). ### Call types | Type | Description | Example trigger | |------|-------------|-----------------| | `reminder` | Reminds the invitee of their upcoming meeting, offers to confirm or reschedule | 1 hour before meeting | | `confirmation` | Confirms the booking was made, reads back the details | Immediately after booking | | `follow_up` | Follows up after a meeting, offers to schedule another | 1 day after meeting | | `custom` | Custom message you define | Any trigger | ### AI_CALL action configuration ```json // 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 | Variable | Required | Description | |----------|----------|-------------| | `ELEVENLABS_API_KEY` | Yes | ElevenLabs API key ([get one here](https://elevenlabs.io/app/settings/api-keys)) | | `ELEVENLABS_WEBHOOK_SECRET` | Yes | Shared secret for tool webhook auth (set in ElevenLabs agent config) | | `ELEVENLABS_SIGNING_SECRET` | Yes | HMAC secret for post-call webhook signature verification | | `TWILIO_ACCOUNT_SID` | For phone | Twilio Account SID ([Twilio Console](https://console.twilio.com)) | | `TWILIO_AUTH_TOKEN` | For phone | Twilio Auth Token | --- # SMS & WhatsApp Messaging Agents > AI-powered text-based booking agents that handle SMS and WhatsApp conversations, check availability, and book meetings -- powered by Claude AI and Twilio. > Source: https://linktime.io/docs/ai/messaging > Last updated: February 2026 ## Overview Messaging Agents add a conversational AI booking assistant to your LinkTime account via text. The assistant can: - Respond to inbound SMS and WhatsApp messages in real time - Carry multi-turn conversations with full context across messages - List your available meeting types - Check real-time availability on your calendar - Book meetings, create contacts, and send confirmations - Handle STOP/HELP opt-out compliance (required by telecom regulations) ## How It Works ``` Customer texts your Twilio number -> Twilio webhook POST /api/webhooks/twilio/inbound -> Routes to MessagingAgent (state machine + Claude AI) -> Claude processes conversation with tools: -> list_event_types (show meeting types) -> get_available_slots (check calendar) -> create_booking (book meeting) -> Response sent back via Twilio SMS or WhatsApp API ``` Each agent has: name, greeting, tone (professional/friendly/casual/custom), language, linked event types, and a phone number. ### Conversation State Machine Messages flow through a state machine that tracks where the customer is in the booking flow: 1. **Greeting** -- Agent introduces itself 2. **Event selection** -- Customer picks a meeting type 3. **Date/time selection** -- Agent shows available slots 4. **Information collection** -- Name, email, any required fields 5. **Confirmation** -- Booking created, confirmation sent Context carries forward across messages so the customer never has to repeat themselves. ## SMS Agents - Work with any provisioned Twilio phone number - No additional setup beyond phone number provisioning - ~$0.0079/message US rate (varies by country) - STOP/HELP keyword compliance built in - Immediate delivery, no approval process ## WhatsApp Agents - Require WhatsApp Business sender registration on the phone number - Automated registration via Twilio Channels Senders API (v2.44.0) - Meta approval required: 1-3 business days - Template notifications for booking confirmations and reminders - Falls back to SMS when WhatsApp templates are pending or rejected ## WhatsApp Registration (v2.44.0) Self-service flow for enabling WhatsApp on a phone number: 1. Go to Phone Numbers dashboard and click "Enable WhatsApp" on your number 2. Enter your business display name (must follow Meta naming rules) 3. Status changes to **Pending** -- Meta reviews the display name 4. Meta approves or rejects (typically 1-3 business days) 5. Email notification sent on approval or rejection 6. WhatsApp agents can activate once the number is approved ### Status Tracking Two complementary mechanisms ensure status updates are never missed: - **Webhook (instant):** Twilio sends status changes to `/api/webhooks/twilio/whatsapp-sender-status` (signature-verified) - **Cron polling (fallback):** `/api/cron/whatsapp-status` polls every 5 minutes for pending senders, hourly for approved senders (revocation detection) ### API **Register a WhatsApp sender:** ``` POST /api/agents/phone-numbers/{id}/whatsapp Content-Type: application/json { "displayName": "Dr. Smith Dental" } ``` **Check registration status:** ``` GET /api/agents/phone-numbers/{id}/whatsapp GET /api/agents/phone-numbers/{id}/whatsapp?refresh=true (live check from Twilio) ``` **Deregister WhatsApp sender:** ``` DELETE /api/agents/phone-numbers/{id}/whatsapp ``` ### Status Mapping (Twilio to LinkTime) | Twilio Status | LinkTime Status | WhatsApp Enabled | |---------------|----------------|------------------| | CREATING, PENDING_VERIFICATION, VERIFYING, TWILIO_REVIEW, DRAFT, STUBBED | pending | No | | ONLINE, ONLINE:UPDATING | approved | Yes | | OFFLINE (with senderSid) | rejected | No | ## Display Name Rules (Meta Requirements) Your WhatsApp business display name must follow Meta's naming policy: **Must:** - Be 3-256 characters - Contain at least one letter - Represent your actual business name **Must NOT contain:** - Special characters: `~!@#$%^&*()_+:;"'{}[]|<>,/?` - URLs or web addresses - Meta product names: WhatsApp, Facebook, Instagram, Messenger - "Official" or "Verified" - Generic terms alone (e.g., just "Store" or "Business") **Valid examples:** "Dr. Smith Dental", "Acme Corp", "Main Street Salon" **Invalid examples:** "WhatsApp Business", "Official Store", "test@company" ## Phone Numbers Phone numbers are shared infrastructure -- one number can serve voice agents, SMS agents, and WhatsApp agents simultaneously. ### Provisioning ``` POST /api/agents/phone-numbers Content-Type: application/json { "countryCode": "US", "label": "Main Office" } ``` The `countryCode` parameter accepts any of the 14 supported countries (ISO 3166-1 alpha-2 code). LinkTime searches for a local number first, then falls back to toll-free if unavailable. | Country | Code | Dial Code | Approx. Cost | |---------|------|-----------|-------------| | United States | US | +1 | ~$1/mo | | Canada | CA | +1 | ~$1/mo | | United Kingdom | GB | +44 | ~$1/mo | | France | FR | +33 | ~$1/mo | | Germany | DE | +49 | ~$1/mo | | Spain | ES | +34 | ~$1/mo | | Netherlands | NL | +31 | ~$1/mo | | Italy | IT | +39 | ~$1/mo | | Australia | AU | +61 | ~$2/mo | | Brazil | BR | +55 | ~$3/mo | | Mexico | MX | +52 | ~$2/mo | | Japan | JP | +81 | ~$5/mo | | Singapore | SG | +65 | ~$3/mo | | UAE | AE | +971 | ~$5/mo | > **Full country data:** See `src/lib/countries.ts` (`PHONE_COUNTRIES` array) for flag emojis and SMS inbound capabilities. ### Releasing ``` DELETE /api/agents/phone-numbers/{id} ``` Released numbers enter a **30-day hold period** before being recycled by Twilio. WhatsApp sender registration is automatically deregistered on release. ## Plan Limits | Plan | Phone Numbers | SMS/WhatsApp Agents | Monthly Messages | |------|--------------|--------------------|--------------------| | Free | 0 | 0 | 0 | | Pro | 1 | 2 per channel | 100 | | Business | 5 | 10 per channel | 500 | | Enterprise | 20 | 50 per channel | Unlimited | > **Shared message pool:** Your monthly message quota is shared across all SMS and WhatsApp agents. If you have 100 messages on the Pro plan, those 100 messages are consumed by conversations with any of your agents across both channels. ## Agent API All endpoints require Clerk session authentication. ### Phone Numbers | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/agents/phone-numbers` | List all phone numbers | | POST | `/api/agents/phone-numbers` | Provision a new number | | GET | `/api/agents/phone-numbers/{id}` | Get number details | | PUT | `/api/agents/phone-numbers/{id}` | Update number settings | | DELETE | `/api/agents/phone-numbers/{id}` | Release number (30-day hold) | ### WhatsApp Registration | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/api/agents/phone-numbers/{id}/whatsapp` | Register WhatsApp sender | | GET | `/api/agents/phone-numbers/{id}/whatsapp` | Check registration status | | DELETE | `/api/agents/phone-numbers/{id}/whatsapp` | Deregister WhatsApp sender | ### Messaging Agents | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/agents/messaging` | List all messaging agents | | POST | `/api/agents/messaging` | Create a new agent | | GET | `/api/agents/messaging/{id}` | Get agent details | | PUT | `/api/agents/messaging/{id}` | Update agent settings | | DELETE | `/api/agents/messaging/{id}` | Delete agent | ### Create Agent Example ```bash curl -X POST https://linktime.io/api/agents/messaging \ -H "Content-Type: application/json" \ -H "Cookie: __session=" \ -d '{ "name": "Sarah", "greeting": "Hi! I'\''m Sarah, Emmanuel'\''s scheduling assistant. How can I help?", "tone": "friendly", "language": "en", "phoneNumberId": "cm_phone_abc...", "channel": "sms" }' ``` ### Update Agent Example ```bash curl -X PUT https://linktime.io/api/agents/messaging/cm_agent_abc... \ -H "Content-Type: application/json" \ -H "Cookie: __session=" \ -d '{ "name": "Alex", "tone": "professional", "greeting": "Hello, this is Alex from LinkTime. How may I assist you?" }' ``` ## Architecture ### Key Components | Component | File | Purpose | |-----------|------|---------| | State Machine | `src/lib/messaging-state-machine.ts` | Conversation state transitions, tool preconditions, context schema | | Core Agent | `src/lib/messaging-agent.ts` | Conversational loop, rate limiter, circuit breaker, cost tracking | | Prompt Builder | `src/lib/messaging-agent-prompt.ts` | System prompt with context carry-forward and personality guardrails | | Tool Adapter | `src/lib/messaging-tools.ts` | Wraps voice-tools for Claude tool_use format, timezone resolution, idempotency | | Phone Provisioning | `src/lib/messaging-phone.ts` | Buy, configure smsUrl, release with 30-day hold | | WhatsApp Sender | `src/lib/whatsapp-sender.ts` | Twilio Senders API wrapper, validation, status mapping, notifications | | Inbound Webhook | `src/app/api/webhooks/twilio/inbound/route.ts` | STOP/HELP compliance + messaging agent routing | ### Tools Available to Claude The messaging agent uses the same underlying tool handlers as the voice agent: | Tool | Description | |------|-------------| | `list_event_types` | Returns the user's active, non-secret event types | | `get_available_slots` | Checks real-time calendar availability for a date | | `create_booking` | Creates a confirmed booking and sends confirmation | ### Safety Features - **Rate limiter:** Per-phone-number rate limiting to prevent abuse - **Circuit breaker:** Automatic fallback when Claude AI is unavailable - **Cost tracking:** Per-message cost logged for billing reconciliation - **Idempotency:** Duplicate message detection prevents double-bookings ## Security ### Twilio Signature Verification All inbound webhooks verify the `x-twilio-signature` header using `validateRequest()` from the Twilio SDK. This ensures messages genuinely originate from Twilio. ### WhatsApp Sender Status Webhook Sender registration status callbacks at `/api/webhooks/twilio/whatsapp-sender-status` use the same Twilio signature verification. ### STOP/HELP Compliance Required by US telecom regulations (TCPA/CTIA guidelines): - **STOP:** Immediately opts out the sender. No further messages are sent. Acknowledged with "You've been unsubscribed." - **HELP:** Returns contact information and opt-out instructions. - Opt-out status is checked before every outbound message (SMS and WhatsApp). - Opt-out records are stored in the `SmsOptOut` table and shared across all channels. ### User Data Isolation Each messaging agent is scoped to its owner's data. An agent can only access its owner's event types, calendar, and bookings. Cross-user data access is impossible by design. ### Rate Limiting Per-user rate limiting prevents excessive API usage. Limits are enforced at the inbound webhook level. ### Circuit Breaker If Claude AI becomes unavailable, the circuit breaker trips after consecutive failures. During this period, inbound messages receive a fallback response directing the sender to book online at the user's booking URL. ### Admin Kill Switch The `MESSAGING_AGENT_ENABLED` environment variable acts as a global kill switch. When set to `false`, all messaging agents stop responding. Individual agents can be disabled via the dashboard or API. ## Troubleshooting ### "WhatsApp API not yet available" Twilio requires at least one WhatsApp sender registered manually via Twilio Console before the API is available. This is a one-time setup per Twilio account. Error code 21656 is caught and displayed as a clear admin message. ### WhatsApp stuck on "Pending" Meta typically reviews display names within 1-3 business days. Click "Refresh Status" to poll Twilio for the latest. If stuck for 5+ days, try deregistering and re-registering with a different display name. ### WhatsApp "Rejected" The display name did not meet Meta's naming policy. Click "Try Again" and enter a different name that follows the display name rules above. ### Agent not responding to messages Check the following: 1. Agent is set to **active** in the dashboard 2. Phone number is provisioned and correctly linked 3. WhatsApp sender is **approved** (if using WhatsApp channel) 4. Sender has not opted out (check `SmsOptOut` table) 5. `MESSAGING_AGENT_ENABLED` is not set to `false` 6. `ANTHROPIC_API_KEY` is valid ### Messages failing to send Check Twilio geo-permissions for the destination country. Go to Twilio Console > Messaging > Settings > Geo Permissions and enable the target countries. ### "Circuit breaker open" in logs Claude AI experienced consecutive failures. The circuit breaker will automatically reset after a cooldown period. Check the `ANTHROPIC_API_KEY` is valid and the Anthropic API status page for outages. ### Duplicate bookings This should not happen -- the tool adapter includes idempotency checks. If it does occur, check for duplicate inbound webhook deliveries from Twilio and verify the rate limiter is functioning. ## Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `TWILIO_ACCOUNT_SID` | Yes | Twilio Account SID ([Twilio Console](https://console.twilio.com)) | | `TWILIO_AUTH_TOKEN` | Yes | Twilio Auth Token | | `ANTHROPIC_API_KEY` | Yes | Claude AI API key for conversation processing ([Anthropic Console](https://console.anthropic.com)) | | `MESSAGING_AGENT_ENABLED` | No | Global kill switch for all messaging agents (default: `true`) | | `ADMIN_KILL_SECRET` | No | Secret token for admin override endpoints (min 8 chars) | --- # Workflows > Source: https://linktime.io/docs/automation/workflows ## Overview LinkTime Workflows let you automate repetitive tasks around your bookings. When something happens (like a booking is created), workflows run actions automatically — sending emails, Slack messages, SMS, or calling webhooks. No coding required. ## How Workflows Work Every workflow has two parts: 1. **Trigger** — the event that starts the workflow 2. **Actions** — what happens next (up to 5 per workflow) Think of it as: "When X happens, do Y, then Z." ## Triggers | Trigger | Description | |---------|-------------| | Booking Created | Fires when someone books a meeting | | Booking Cancelled | Fires when a booking is cancelled | | Booking Rescheduled | Fires when a meeting is rescheduled | | Payment Received | Fires when a paid booking payment completes | | Before Meeting | Fires a configurable time before the meeting (15 min, 1 hour, etc.) | | After Meeting | Fires after the meeting ends | ## Actions | Action | Description | |--------|-------------| | Send Email | Send a custom email to the booker, host, or any address | | Send SMS | Send a text message (requires Twilio) | | Send Slack Message | Post to a Slack channel | | Call Webhook | Send an HTTP POST to any URL with booking data | | AI Voice Call | Trigger an outbound call via your AI voice agent | ## Creating a Workflow 1. Go to Dashboard → Workflows 2. Click "Create Workflow" or start from a template 3. Choose a trigger 4. Add one or more actions (up to 5) 5. Test it, then enable ## Templates LinkTime includes 6 ready-to-use templates: - **Thank You Email** — sends a thank-you after booking - **Meeting Reminder** — SMS reminder before the meeting - **Team Notification** — Slack message when a booking is created - **Payment Confirmation** — email after payment received - **Cancellation Follow-up** — email when someone cancels - **Post-Meeting Survey** — email after the meeting ends ## Plan Limits | Feature | Free | Pro | Business | |---------|------|-----|----------| | Active workflows | 3 | 10 | Unlimited | | Actions per workflow | 5 | 5 | 5 | | SMS actions | No | Yes | Yes | | AI Voice Call action | No | No | Yes | | Webhook actions | Yes | Yes | Yes | ## Common Examples 1. **Slack notification on booking** — Trigger: Booking Created → Action: Send Slack Message 2. **SMS reminder 1 hour before** — Trigger: Before Meeting (1 hour) → Action: Send SMS 3. **Post-meeting survey** — Trigger: After Meeting → Action: Send Email 4. **CRM webhook** — Trigger: Booking Created → Action: Call Webhook ## Webhook Payload When using the "Call Webhook" action, LinkTime sends a JSON POST with booking data. See the [Webhooks API documentation](https://linktime.io/docs/developers/webhooks) for payload format details. ## Troubleshooting - **Workflow didn't fire**: Check that the workflow is enabled (green toggle) and the trigger matches the event. - **SMS action failed**: Verify Twilio is connected in Integrations. Check SMS credits. - **Webhook not receiving data**: Confirm the URL is correct and publicly accessible. Check webhook logs in Settings. --- # Make.com Integration > Connect LinkTime to 1000+ apps using Make.com (formerly Integromat) and webhooks. > Source: https://linktime.io/docs/automation/make > Last updated: February 2026 ## Overview LinkTime webhooks allow you to receive real-time notifications when bookings are created, cancelled, rescheduled, or paid. Combined with Make.com, you can automate workflows like: - Add new invitees to your CRM (HubSpot, Salesforce, Pipedrive) - Send Slack or Teams notifications for new bookings - Create tasks in Asana, Notion, or Monday.com - Add contacts to email lists (Mailchimp, ConvertKit) - Log meeting activity in spreadsheets ## Step 1: Create a Webhook in LinkTime 1. **Go to Webhooks Settings** - Navigate to Settings -> Webhooks in your LinkTime dashboard. 2. **Click "Create Webhook"** - You'll need to provide a URL (we'll get this from Make.com in Step 2) and select which events to receive. 3. **Copy the Webhook Secret** - After creation, you'll see the webhook secret once. Save it somewhere secure -- you'll need it to verify webhook signatures (optional but recommended). ## Step 2: Set Up Make.com Scenario 1. **Create a New Scenario** - Log in to [Make.com](https://www.make.com) and create a new scenario. 2. **Add a "Custom Webhook" Trigger** - Search for "Webhooks" in the app list and select "Custom webhook". Click "Add" to create a new webhook. 3. **Copy the Webhook URL** - Make.com will generate a unique URL like `https://hook.make.com/abc123...` 4. **Paste URL in LinkTime** - Go back to LinkTime and paste this URL when creating your webhook. Save the webhook. 5. **Send a Test Webhook** - In LinkTime, click the "Test" button on your webhook. This sends a sample payload to Make.com, which will automatically detect the data structure. ## Available Webhook Events | Event | Description | |-------|-------------| | `booking.created` | Triggered when a new booking is confirmed | | `booking.cancelled` | Triggered when a booking is cancelled | | `booking.rescheduled` | Triggered when a booking is moved to a new time | | `payment.received` | Triggered when payment is completed for a paid booking | | `contact.created` | Triggered when a new contact is added (from booking or manually) | ## Webhook Payload Structure All webhook payloads follow this structure. You can use these fields in Make.com to map data to other apps. ```json { "event": "booking.created", "timestamp": "2026-02-06T15:30:00.000Z", "data": { "id": "clx123abc...", "eventType": { "id": "clx456def...", "name": "30 Minute Meeting", "slug": "30-min", "duration": 30 }, "startTime": "2026-02-07T14:00:00.000Z", "endTime": "2026-02-07T14:30:00.000Z", "status": "CONFIRMED", "invitee": { "name": "John Doe", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "usr_abc123", "name": "Jane Smith", "email": "jane@company.com" }, "meetingLink": "https://meet.google.com/abc-xyz-123", "location": null, "notes": "Looking forward to discussing...", "isRecurring": false, "recurringGroupId": null } } ``` ### Additional Fields by Event **booking.cancelled** - Includes `cancelReason` if provided. **booking.rescheduled** - Includes `previousStartTime` and `previousEndTime`. **payment.received** - Different structure: `bookingId`, `amount`, `currency`, `paymentMethod`, `transactionId`. **contact.created** - Contact fields: `id`, `name`, `email`, `phone`, `company`, `notes`, `source` (booking/manual/payment). ## Common Automation Scenarios ### Slack Notification for New Bookings LinkTime Webhook -> Slack: Send Message Use `data.invitee.name` and `data.startTime` in your Slack message. ### Add Contact to HubSpot CRM LinkTime Webhook -> HubSpot: Create Contact Map `data.invitee.email` to Email and `data.invitee.name` to Name. ### Log to Google Sheets LinkTime Webhook -> Google Sheets: Add Row Create columns for Name, Email, Event Type, Date, and map the corresponding fields. ### Create Asana Task on Cancellation LinkTime Webhook -> Filter: booking.cancelled -> Asana: Create Task Use a Filter module to only process `event = booking.cancelled`, then create a follow-up task. ## Security: Verifying Webhook Signatures Every webhook includes an HMAC-SHA256 signature in the `X-LinkTime-Signature` header. While optional, verifying signatures ensures the webhook came from LinkTime. > **Headers included with every webhook:** > - `X-LinkTime-Signature` -- HMAC-SHA256 signature (sha256=...) > - `X-LinkTime-Event` -- Event type (booking.created, etc.) > - `X-LinkTime-Timestamp` -- ISO 8601 timestamp To verify in Make.com, you can use a "Custom function" or "Code" module with this logic: ```javascript // JavaScript example for verification const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); return signature === expected; } // Usage: // payload = raw JSON body as string // signature = X-LinkTime-Signature header value // secret = your webhook secret (whsec_...) ``` For most Make.com scenarios, signature verification is optional since Make.com URLs are unique and not easily guessable. ## Troubleshooting ### Webhook not receiving data Make sure your Make.com scenario is "Active" (turned on). Check the webhook delivery history in LinkTime -> Settings -> Webhooks -> Click on your webhook to see recent deliveries. ### Make.com shows "Waiting for data" Click the "Test" button on your webhook in LinkTime. This sends a sample payload that Make.com will use to detect the data structure. ### Deliveries showing errors Check the status code in the delivery history. A 400 error usually means Make.com couldn't parse the data. Try sending another test webhook. A timeout error means Make.com took too long to respond (keep scenarios simple for fastest processing). --- # Zapier Integration > Connect LinkTime to 7,000+ apps using native Zapier triggers. No code required. > Source: https://linktime.io/docs/automation/zapier > Last updated: February 2026 ## What You Can Do LinkTime's native Zapier app gives you instant (real-time) triggers for all booking events. Unlike generic webhook integrations, you get a first-class experience with: - Instant triggers -- no polling delays, events fire in real-time - Event-type filtering -- only trigger for specific event types - Dedicated rescheduled event -- no workaround needed (cancel + create) - Payment and contact triggers -- beyond just bookings - Available on all plans -- including Free ## Architecture Overview LinkTime uses Zapier's **REST Hook** model -- the fastest and most reliable trigger type Zapier offers. Here's how it works: ``` 1. You create a Zap with a LinkTime trigger 2. Zapier sends a subscribe request to LinkTime's API 3. LinkTime creates a webhook pointing to Zapier's hook URL 4. When an event happens (e.g., booking created): a. LinkTime fires the webhook to Zapier's URL b. Zapier receives the payload instantly c. Your Zap actions run (Slack message, CRM update, etc.) 5. If you turn off the Zap, Zapier sends an unsubscribe request 6. LinkTime deletes the webhook automatically ``` This is the same model used by Stripe, GitHub, and other top Zapier integrations. There's no polling -- events arrive in real-time, typically within 1-2 seconds. ## Setup Guide ### Step 1: Generate an API Key 1. **Go to API Keys Settings** - Navigate to Settings -> API Keys in your LinkTime dashboard. 2. **Create a new API key** - Click "Create API Key", give it a name like "Zapier Integration", and copy the key immediately. You won't be able to see it again. ### Step 2: Connect in Zapier 1. **Find LinkTime in Zapier** - Go to zapier.com and create a new Zap. Search for "LinkTime" as your trigger app, or use the invite link shared by our team (the app is currently private/invite-only). 2. **Enter your API key** - Paste the API key you generated in Step 1. Zapier will verify it works and show "Connected as [your name]". 3. **Choose a trigger** - Select one of the 5 available triggers (see reference below). Optionally filter by event type for booking triggers. ## API Key Management API keys authenticate your Zapier connection to LinkTime. Each key starts with `lt_live_` and is 72 characters long. ### Plan Limits | Plan | API Keys | |------|----------| | Free | 2 keys | | Pro | Unlimited | | Organization | Unlimited | ### Key Rotation (Zero Downtime) All plans allow at least 2 simultaneous keys, so you can rotate without disrupting your Zaps: 1. Create a new API key 2. Update the key in Zapier's connection settings 3. Verify your Zaps still work 4. Delete the old key > **Security Best Practices:** > - Never share API keys in public repositories or chat messages > - Rotate keys every 90 days (recommended) or immediately if compromised > - Use a descriptive name for each key (e.g., "Zapier - Sales Automation") > - Keys are hashed with SHA-256 before storage -- we never store the raw key ## Trigger Reference | Trigger | Event Key | Description | Event Filter | |---------|-----------|-------------|--------------| | Booking Created | `booking.created` | Fires instantly when a new booking is confirmed. | Yes | | Booking Cancelled | `booking.cancelled` | Fires instantly when a booking is cancelled. | Yes | | Booking Rescheduled | `booking.rescheduled` | Fires instantly when a booking is moved to a new time. Includes both old and new times. | Yes | | Payment Received | `payment.received` | Fires when a payment is completed for a paid booking. | - | | New Contact | `contact.created` | Fires when a new contact is added (from a booking, payment, or manually). | - | ## Payload Examples All payloads share an envelope with `apiVersion`, `event`, `timestamp`, and `data`. The `data` field varies by event type. ### `booking.created` ```json { "apiVersion": "2025-02-09", "event": "booking.created", "timestamp": "2026-02-09T10:00:00.000Z", "data": { "id": "cm6abc123def456", "eventType": { "id": "cm6evt789ghi012", "name": "30-Minute Consultation", "slug": "30-min-consultation", "duration": 30 }, "startTime": "2026-02-10T14:00:00.000Z", "endTime": "2026-02-10T14:30:00.000Z", "status": "confirmed", "invitee": { "name": "John Smith", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "user_abc123", "name": "Sarah Johnson", "email": "sarah@company.com" }, "meetingLink": "https://meet.google.com/abc-defg-hij", "location": "Google Meet", "notes": "Looking forward to discussing the project.", "isRecurring": false, "recurringGroupId": null } } ``` ### `booking.cancelled` Same structure as `booking.created`, plus an optional `cancelReason` field: ```json { "apiVersion": "2025-02-09", "event": "booking.cancelled", "timestamp": "2026-02-09T15:30:00.000Z", "data": { "id": "cm6abc123def456", "eventType": { "id": "...", "name": "...", "slug": "...", "duration": 30 }, "startTime": "2026-02-10T14:00:00.000Z", "endTime": "2026-02-10T14:30:00.000Z", "status": "cancelled", "invitee": { "name": "John Smith", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "user_abc123", "name": "Sarah Johnson", "email": "sarah@company.com" }, "cancelReason": "Schedule conflict -- need to reschedule next week.", ... } } ``` ### `booking.rescheduled` Includes the new time in `startTime`/`endTime` and the old time in `previousStartTime`/`previousEndTime`: ```json { "apiVersion": "2025-02-09", "event": "booking.rescheduled", "timestamp": "2026-02-09T16:00:00.000Z", "data": { "id": "cm6abc123def456", "eventType": { "id": "...", "name": "...", "slug": "...", "duration": 30 }, "startTime": "2026-02-12T10:00:00.000Z", "endTime": "2026-02-12T10:30:00.000Z", "previousStartTime": "2026-02-10T14:00:00.000Z", "previousEndTime": "2026-02-10T14:30:00.000Z", "status": "confirmed", "invitee": { "name": "John Smith", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "user_abc123", "name": "Sarah Johnson", "email": "sarah@company.com" }, ... } } ``` ### `payment.received` ```json { "apiVersion": "2025-02-09", "event": "payment.received", "timestamp": "2026-02-09T10:05:00.000Z", "data": { "bookingId": "cm6abc123def456", "amount": 5000, "currency": "usd", "paymentMethod": "card", "transactionId": "pi_3abc123def456" } } ``` **Note:** Amount is in cents (5000 = $50.00). ### `contact.created` ```json { "apiVersion": "2025-02-09", "event": "contact.created", "timestamp": "2026-02-09T10:00:00.000Z", "data": { "id": "cm6contact789", "name": "John Smith", "email": "john@example.com", "phone": "+1-555-0123", "company": "Acme Corp", "notes": null, "createdAt": "2026-02-09T10:00:00.000Z", "source": "booking" } } ``` The `source` field can be `"booking"`, `"manual"`, or `"payment"`. ### Output Field Types | Field | Type | Notes | |-------|------|-------| | id | string | CUID format | | startTime / endTime | string (ISO 8601) | Always UTC | | duration | number | Minutes | | amount | number | Cents (5000 = $50.00) | | isRecurring | boolean | true/false | | cancelReason, notes, etc. | string \| null | Optional fields | ## Event-Type Filtering When setting up a booking trigger (created, cancelled, or rescheduled), you can optionally select a specific event type from a dropdown. This means the Zap will only fire for bookings of that type -- perfect if you want different workflows for different meeting types. **Example:** You have "Sales Demo" and "Support Call" event types. You want new sales demos to go to Salesforce, but support calls to go to Zendesk. Create two Zaps with different event-type filters -- each fires only for the right type. > **Tip:** Leave the event-type filter empty to trigger for all event types -- useful for general notifications like "post every new booking to Slack." ## Security & Rate Limits ### API Key Security - Keys are hashed with SHA-256 before database storage -- raw keys are never persisted - Authentication via `X-API-Key` header - Each key is scoped to a single user account ### Webhook Payload Signing All webhook payloads (including those sent to Zapier) include an `X-LinkTime-Signature` header with an HMAC-SHA256 signature. This allows you to verify that payloads genuinely came from LinkTime. ### Rate Limits | Tier | Limit | Scope | |------|-------|-------| | Pre-auth (invalid key) | 5 requests/min | Per IP address | | Post-auth (valid key) | 60 requests/min | Per user | Zapier's subscribe/unsubscribe and performList calls count against the post-auth limit. Under normal usage, you won't hit these limits. ## Error Reference | Status | Meaning | Action | |--------|---------|--------| | 401 | Invalid or missing API key | Check key is correct and active | | 403 | Plan limit reached (e.g., max API keys or webhooks) | Upgrade plan or delete unused keys/webhooks | | 429 | Rate limit exceeded | Wait and retry (Zapier handles this automatically) | | 500 | Server error | Retry after a moment; contact support if persistent | ## Payload Versioning Every payload includes an `apiVersion` field (currently `"2025-02-09"`). ### Compatibility Promise - **Adding new fields** -- always safe, never requires a version bump - **Renaming or removing fields** -- requires a new apiVersion - **Changing field types** -- requires a new apiVersion Your Zaps should be resilient to new fields being added at any time. Zapier's platform handles this gracefully -- new fields simply appear as available mapping options. ## Popular Workflows | Category | Workflow | App | |----------|----------|----| | CRM | New Booking to HubSpot Contact | HubSpot | | CRM | New Booking to Salesforce Lead | Salesforce | | Notifications | New Booking to Slack Channel | Slack | | Notifications | Booking Cancelled to Slack Alert | Slack | | Data | New Booking to Google Sheets Row | Google Sheets | | Marketing | New Contact to Mailchimp List | Mailchimp | | Notifications | Payment Received to Slack Notification | Slack | | Data | New Booking to Notion Database | Notion | ## LinkTime vs. Competitors on Zapier | Feature | Calendly | Cal.com | LinkTime | |---------|----------|---------|----------| | Event-type filter | No | No | Yes | | Rescheduled trigger | No | Yes | Yes | | Payment trigger | No | Yes | Yes | | Contact trigger | No | No | Yes | | Free plan access | No | Yes | Yes | ## Troubleshooting ### Connection test fails Make sure you copied the full API key (starts with `lt_live_`, 72 characters total). Check that the key is active in Settings -> API Keys. ### Zap doesn't fire Check if you have an event-type filter set -- the trigger will only fire for that specific event type. Also verify the Zap is turned on (not paused) in Zapier. ### Webhook limit reached Each Zap creates a webhook. Free plans allow 2 total webhooks (manual + Zapier combined). Upgrade to Pro for unlimited. ### I see a "Zapier" webhook in my settings That's expected! When you create a Zap, LinkTime creates a webhook behind the scenes. These are labeled "Zapier" in your webhook list and are managed automatically -- they'll be removed when you turn off the Zap. ### How do I rotate my API key? See the [API Key Management](#api-key-management) section above for the zero-downtime rotation process. ### "LinkTime" doesn't appear in Zapier search The LinkTime Zapier app is currently invite-only (not yet published to the public marketplace). Ask our team for the invite link, or check your email for an invitation. --- # API Reference > LinkTime REST API — 12 authenticated endpoints + 1 public booking endpoint. > Source: https://linktime.io/docs/developers/api > Last updated: February 2026 ## Getting Started 1. Upgrade to Pro or Business plan 2. Go to Settings → API Keys → Create API Key 3. Copy the key (shown once — cannot be retrieved later) 4. Test: `curl -H "Authorization: Bearer YOUR_KEY" https://linktime.io/api/v1/me` API keys start with `lt_live_`. Each key is scoped to the user who created it. ## Authentication All `/api/v1/*` endpoints require a Bearer token: ``` Authorization: Bearer lt_live_... ``` ## Endpoints ### User | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/me` | Current user info (id, email, name, username, plan, effectivePlan) | ### Bookings | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/bookings` | List bookings. Params: `page`, `limit` (max 100), `status` (CONFIRMED\|CANCELLED\|all), `from`, `to` (ISO dates) | | `GET` | `/api/v1/bookings/:id` | Booking detail with event type, contact, and email logs | | `POST` | `/api/v1/bookings/:id/cancel` | Cancel a booking. Body: `{ "reason": "..." }`. Fires all side effects (calendar, CRM, email, webhooks). | ### Contacts | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/contacts` | List/search contacts. Params: `search`, `page`, `limit` | | `POST` | `/api/v1/contacts` | Create a contact. Body: `{ "name", "email", "phone?", "company?", "notes?" }`. Fires `contact.created` webhook. | | `GET` | `/api/v1/contacts/:id` | Contact detail with upcoming/past meetings | | `PATCH` | `/api/v1/contacts/:id` | Update a contact (partial). Email uniqueness enforced. | | `DELETE` | `/api/v1/contacts/:id` | Delete a contact | ### Event Types | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/event-types` | List active event types | | `GET` | `/api/v1/event-types/:id` | Event type detail (includes scheduling config) | ### Availability | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/availability/:slug` | Available time slots. Params: `timezone` (required), `startDate`, `endDate` | ### Public Booking (No Auth Required) | Method | Path | Description | |--------|------|-------------| | `POST` | `/api/bookings` | Create a booking. Body: `{ "eventTypeId", "inviteeName", "inviteeEmail", "inviteeTimezone", "startTime" }` | ## Response Format ### Single Resource ```json { "id": "...", "name": "...", "createdAt": "2026-02-14T10:00:00.000Z" } ``` ### List with Pagination ```json { "data": [...], "pagination": { "page": 1, "limit": 20, "total": 156, "totalPages": 8 } } ``` ### Error ```json { "error": "message", "details": { "field": "validation message" } } ``` ## Rate Limits - **API key endpoints:** 60 requests/minute per API key - **Pre-auth protection:** 5 requests/minute per IP - **Public booking:** 10 requests/minute per IP 429 responses include a `Retry-After` header. ## HTTP Status Codes | Status | Meaning | |--------|---------| | 200 | Success | | 201 | Created (POST /contacts) | | 400 | Bad request | | 401 | Authentication failed | | 404 | Not found or not owned by you | | 409 | Conflict (duplicate email) | | 429 | Rate limited | | 500 | Server error | ## Webhook Events Configure webhooks in your dashboard (not via API). See [Webhooks API](https://linktime.io/docs/developers/webhooks). | Event | Description | |-------|-------------| | `booking.created` | A new booking has been confirmed | | `booking.cancelled` | A booking has been cancelled | | `booking.rescheduled` | A booking has been moved to a new time | | `payment.received` | Payment completed for a paid booking | | `contact.created` | A new contact was added | ## Related Documentation - [Webhooks API](https://linktime.io/docs/developers/webhooks) — Full payload reference and signature verification - [Embed Widget](https://linktime.io/docs/developers/embed) — Add booking to your website - [Make.com Integration](https://linktime.io/docs/automation/make) — Connect to 1000+ apps - [Quickstart Guide](https://linktime.io/docs/getting-started/quickstart) — Get started in 5 minutes --- # Webhooks API > Receive real-time HTTP notifications when events occur in LinkTime. > Source: https://linktime.io/docs/developers/webhooks > Last updated: February 2026 ## Events Subscribe to any of the following events when creating a webhook: | Event | Description | |-------|-------------| | `booking.created` | A new booking has been confirmed | | `booking.cancelled` | A booking has been cancelled | | `booking.rescheduled` | A booking has been moved to a new time | | `payment.received` | Payment completed for a paid booking | | `contact.created` | A new contact has been added | ## Payload Structure All webhook payloads follow this structure: ```json { "event": "booking.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { // Event-specific data (see below) } } ``` ### HTTP Headers Each webhook request includes these headers: | Header | Description | |--------|-------------| | `Content-Type` | application/json | | `X-LinkTime-Signature` | HMAC-SHA256 signature for verification | | `X-LinkTime-Event` | The event type (e.g., booking.created) | | `X-LinkTime-Timestamp` | ISO 8601 timestamp of the event | | `User-Agent` | LinkTime-Webhook/1.0 | ## Event Payloads ### `booking.created` ```json { "event": "booking.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "id": "clx1234567890", "eventType": { "id": "clx0987654321", "name": "30 Minute Meeting", "slug": "30-min", "duration": 30 }, "startTime": "2026-02-08T10:00:00.000Z", "endTime": "2026-02-08T10:30:00.000Z", "status": "CONFIRMED", "invitee": { "name": "John Smith", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "user_abc123", "name": "Jane Doe", "email": "jane@company.com" }, "meetingLink": "https://meet.google.com/abc-defg-hij", "location": "Google Meet", "notes": "Looking forward to our call!", "isRecurring": false, "recurringGroupId": null } } ``` ### `booking.cancelled` Same as `booking.created`, plus: ```json { "data": { "status": "CANCELLED", "cancelReason": "Schedule conflict" } } ``` ### `booking.rescheduled` Same as `booking.created`, plus previous times: ```json { "data": { "startTime": "2026-02-09T14:00:00.000Z", "endTime": "2026-02-09T14:30:00.000Z", "previousStartTime": "2026-02-08T10:00:00.000Z", "previousEndTime": "2026-02-08T10:30:00.000Z" } } ``` ### `payment.received` ```json { "event": "payment.received", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "bookingId": "clx1234567890", "amount": 50.00, "currency": "USD", "paymentMethod": "stripe", "transactionId": "pi_3ABC123DEF456" } } ``` ### `contact.created` ```json { "event": "contact.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "id": "clx1234567890", "name": "John Smith", "email": "john@example.com", "phone": "+14155551234", "company": "Acme Inc", "notes": null, "createdAt": "2026-02-07T14:30:00.000Z", "source": "booking" } } ``` ## Signature Verification Each webhook includes an HMAC-SHA256 signature in the `X-LinkTime-Signature` header. Always verify this signature to ensure the webhook came from LinkTime. > **Security:** Never process webhooks without verifying the signature. Your webhook secret is shown once when you create the webhook - store it securely. ### Verification Steps 1. Get the raw request body (as a string, not parsed JSON) 2. Compute HMAC-SHA256 of the body using your webhook secret 3. Compare with the signature header (use timing-safe comparison) ### Example: Node.js ```typescript import crypto from 'crypto'; function verifyWebhook(payload: string, signature: string, secret: string): boolean { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); // Timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // In your webhook handler: app.post('/webhooks/linktime', (req, res) => { const signature = req.headers['x-linktime-signature']; const isValid = verifyWebhook(req.rawBody, signature, process.env.LINKTIME_WEBHOOK_SECRET); if (!isValid) { return res.status(401).send('Invalid signature'); } const event = req.body; // Process the webhook... res.status(200).send('OK'); }); ``` ### Example: Python ```python import hmac import hashlib def verify_webhook(payload: bytes, signature: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) # In your Flask handler: @app.route('/webhooks/linktime', methods=['POST']) def handle_webhook(): signature = request.headers.get('X-LinkTime-Signature') is_valid = verify_webhook(request.data, signature, WEBHOOK_SECRET) if not is_valid: return 'Invalid signature', 401 event = request.json # Process the webhook... return 'OK', 200 ``` ## Retry Behavior LinkTime automatically retries failed webhook deliveries to ensure reliability. We use a two-phase retry strategy: ### Phase 1: Immediate Retries During the initial request: - Attempt 1: Immediate - Attempt 2: After 10 seconds - Attempt 3: After 60 seconds ### Phase 2: Background Retries Via scheduled job: - Attempt 4: After 1 hour - Attempt 5: After 4 hours - Attempt 6: After 24 hours ### What Gets Retried | Response | Retried? | Reason | |----------|----------|--------| | 2xx Success | No | Delivery successful | | 4xx Client Error | No | Configuration issue - fix your endpoint | | 429 Rate Limited | Yes | Temporary - will retry later | | 5xx Server Error | Yes | Temporary - will retry later | | Timeout (30s) | Yes | Endpoint slow - will retry later | | Network Error | Yes | DNS/connection issue - will retry later | > **Tip:** Return a 2xx status code as quickly as possible. Process the webhook asynchronously if needed. This prevents timeouts and unnecessary retries. ## Best Practices 1. **Always verify signatures** — Never trust webhooks without verifying the HMAC signature. 2. **Return 200 quickly** — Acknowledge receipt immediately, then process asynchronously. We timeout after 30 seconds. 3. **Handle duplicates** — Due to retries, you may receive the same webhook multiple times. Use the booking ID to deduplicate. 4. **Use HTTPS** — Always use HTTPS endpoints. We do not send webhooks to HTTP URLs in production. 5. **Monitor delivery status** — Check the webhook delivery history in Settings > Webhooks to debug issues. ## Integration Guides - [Make.com](https://linktime.io/docs/automation/make) — Connect to 1000+ apps with Make.com - [Manage Webhooks](https://linktime.io/dashboard/settings/webhooks) — Create and test your webhooks --- # Embed Widget > Add a booking widget directly to your website so visitors can schedule without leaving your page. > Source: https://linktime.io/docs/developers/embed > Last updated: February 2026 ## Overview LinkTime's embed widget lets you add scheduling directly to your website. Visitors can browse your availability, select a time, and complete the booking — all without leaving your site. There are three embed options: - **Inline Embed** — Full calendar view on your page - **Popup Button** — Click to open booking modal - **Direct Link** — Share your booking URL ## Getting Your Embed Code 1. Go to [Event Types](https://linktime.io/dashboard/events) in your dashboard 2. Click the **share** icon on the event type you want to embed 3. Select "Embed" from the sharing options 4. Copy the generated code snippet ## Inline Embed (iframe) The simplest way to embed your booking page. Add this iframe wherever you want the calendar to appear: ```html ``` > **Tip:** Replace `yourusername` with your LinkTime username and `30-min` with your event type slug. ## Popup Button Add a button that opens your booking page in a modal overlay: ```html ``` ## URL Parameters Customize the embed behavior using query parameters: | Parameter | Description | Example | |-----------|-------------|---------| | `embed=true` | Enables embed mode (hides nav) | `?embed=true` | | `name` | Pre-fill invitee name | `&name=John` | | `email` | Pre-fill invitee email | `&email=john@co.com` | | `hideHeader` | Hide the profile header section | `&hideHeader=true` | ## Customization The embed widget automatically inherits your LinkTime branding settings. To customize: - Go to [Settings > Branding](https://linktime.io/dashboard/settings) to change your brand color - Upload a logo and set your display name - Pro users can remove the "Powered by LinkTime" badge ## Responsive Design The embed widget is fully responsive. For best results: - **Width:** Set to `100%` for responsive sizing, or a fixed width (min 320px) - **Height:** Recommended 700px for the full calendar view, 500px minimum - **Mobile:** The widget adapts automatically to smaller screens ## Troubleshooting ### Widget shows a blank page Make sure the URL in the iframe `src` is correct and includes `?embed=true`. Check that the event type is published and not archived. ### Widget is cut off Increase the iframe height. The calendar needs at least 500px of height to display properly. For the full experience, use 700px or more. ### Cross-origin errors in console The embed uses standard iframes which work across all origins. If you see CORS errors, make sure you're using the `https://` version of the URL. --- # Custom Domains > Use your own domain for booking pages instead of linktime.io/username. > Source: https://linktime.io/docs/enterprise/custom-domains > Last updated: February 2026 **Plan requirement:** Organization Plan ## Overview Custom domains let you replace `linktime.io/yourname` with your own branded domain like `meetings.yourcompany.com`. All your booking pages, event types, and scheduling links work seamlessly on your custom domain with automatic SSL. **How it works:** 1. You enter your domain in Settings 2. LinkTime generates DNS records for you to configure 3. You add the records at your DNS provider 4. Click "Verify" once DNS has propagated 5. Your booking pages are now live on your custom domain with SSL ## Before You Start 1. **Organization plan required** -- Custom domains are available exclusively on the Organization plan ($990/yr). You can upgrade from Settings > Plan. 2. **Own a domain** -- You need a domain you control (e.g., yourcompany.com) and access to its DNS settings. 3. **Use a subdomain** -- We recommend using a subdomain like `meetings.yourcompany.com` or `schedule.yourcompany.com`. Root domains (e.g., yourcompany.com) require special DNS support (CNAME flattening). ## Setup Guide ### Step 1: Enter your domain Go to Settings and scroll to the **Custom Domain** section. Enter the subdomain you want to use (lowercase, no spaces). Good examples: - `meetings.yourcompany.com` - `schedule.yourname.io` - `book.agency.co` ### Step 2: Add DNS records After entering your domain, LinkTime will show you **two DNS records** to add at your domain registrar or DNS provider. You need both records -- the TXT proves ownership, and the CNAME routes traffic. **Record 1: TXT (Verification)** This proves you own the domain. Add it exactly as shown in your Settings page. | Field | Value | |-------|-------| | **Type** | TXT | | **Name / Host** | `_linktime-verify.yourdomain.com` | | **Value / Content** | `lt-verify=your-unique-token` | | **TTL** | Auto (or 3600) | **Record 2: CNAME (Routing)** This routes traffic from your domain to LinkTime. SSL is provisioned automatically. | Field | Value | |-------|-------| | **Type** | CNAME | | **Name / Host** | `yourdomain.com` (or just the subdomain part, e.g., `meetings`) | | **Value / Target** | `cname.linktime.io` | | **TTL** | Auto (or 3600) | > **Important:** DNS changes can take anywhere from a few minutes to 48 hours to propagate worldwide. Most changes propagate within 5-30 minutes. ### Step 3: Verify your domain Once you've added both DNS records, go back to Settings and click the **"Verify Domain"** button. LinkTime will: 1. Check your TXT record to confirm domain ownership 2. Provision the domain on our servers (automatic) 3. Generate an SSL certificate (automatic, takes ~1-2 minutes) Once verified, you'll see a green "Verified" badge. Your booking pages are now accessible at your custom domain. ## DNS Provider Guides Here's how to add the DNS records on popular providers. The steps are similar across all providers -- you're adding a TXT record and a CNAME record. ### Cloudflare 1. Log in to your Cloudflare dashboard and select your domain 2. Go to **DNS** > **Records** 3. Click **Add Record** 4. For the TXT record: Type = TXT, Name = `_linktime-verify`, Content = your token 5. For the CNAME record: Type = CNAME, Name = your subdomain (e.g., `meetings`), Target = `cname.linktime.io` 6. **Important:** Set the CNAME Proxy status to **"DNS only"** (grey cloud icon). Do NOT use the orange cloud proxy -- it will interfere with SSL. 7. Click Save for each record ### GoDaddy 1. Log in to GoDaddy and go to **My Products** 2. Find your domain and click **DNS** 3. Click **Add** to create a new record 4. For the TXT record: Type = TXT, Host = `_linktime-verify`, TXT Value = your token, TTL = 1 Hour 5. For the CNAME record: Type = CNAME, Host = your subdomain (e.g., `meetings`), Points to = `cname.linktime.io`, TTL = 1 Hour 6. Click Save for each record ### Namecheap 1. Log in to Namecheap and go to **Domain List** 2. Click **Manage** next to your domain, then **Advanced DNS** 3. Click **Add New Record** 4. For the TXT record: Type = TXT Record, Host = `_linktime-verify`, Value = your token, TTL = Automatic 5. For the CNAME record: Type = CNAME Record, Host = your subdomain (e.g., `meetings`), Value = `cname.linktime.io`, TTL = Automatic 6. Click the checkmark to save each record ### Other providers (Route 53, Google Domains, etc.) The steps are similar across all DNS providers. Look for a "DNS", "DNS Records", or "DNS Management" section in your provider's dashboard. Add the TXT and CNAME records with the values shown in your LinkTime Settings page. If your provider asks for a "Host" or "Name", enter just the subdomain part (e.g., `meetings`), not the full domain. ## What Works on Your Custom Domain **Works on your domain:** - Your profile page (all event types listed) - Individual event booking pages - Time slot selection and booking flow - Automatic SSL (HTTPS) **Stays on linktime.io:** - Dashboard and settings - Sign-in and sign-up - Email links (cancel/reschedule) - Admin panel ## Troubleshooting ### "Verification failed" after adding DNS records DNS propagation can take up to 48 hours, though most changes take 5-30 minutes. Use [dnschecker.org](https://dnschecker.org) to check if your TXT record is visible globally. Search for `_linktime-verify.yourdomain.com` with type TXT. Once it appears in most locations, try verifying again. ### "TXT record found but doesn't match" Double-check the TXT value matches exactly what's shown in Settings. Common mistakes: extra spaces, missing the `lt-verify=` prefix, or copying only part of the token. Delete the old record and create a new one with the exact value from Settings. ### CNAME conflict with existing records A CNAME record cannot coexist with other record types (A, AAAA, MX) on the same name. If you have an A record on the same subdomain, remove it first. This is a DNS protocol rule, not a LinkTime limitation. If you need to use your root domain (e.g., `example.com`), use a DNS provider that supports CNAME flattening (Cloudflare supports this -- they call it "CNAME at apex"). ### SSL certificate error after verification After verification, SSL certificates are provisioned automatically via Let's Encrypt. This typically takes 1-2 minutes. If you see an SSL error, wait 5 minutes and refresh. If using Cloudflare, make sure the CNAME proxy is set to **"DNS only"** (grey cloud), not "Proxied" (orange cloud). ### Domain shows "not found" after verification Check that: (1) your CNAME record points to `cname.linktime.io`, (2) the domain shows a green "Verified" badge in Settings, and (3) you're visiting the correct subdomain (not the root domain). You can verify the CNAME is working by running `dig yourdomain.com CNAME` in a terminal. ### Removing a custom domain Go to Settings > Custom Domain and click "Remove Domain". This immediately removes the domain from LinkTime. Your DNS records will still exist but will have no effect. You can clean them up at your DNS provider whenever convenient. ### Changing to a different domain Remove your current domain first (Settings > Custom Domain > Remove), then set up the new domain. You can only have one custom domain at a time. ## Current Limitations - **One domain per user** -- Each user can configure one custom domain at a time. - **Subdomains recommended** -- Root domains (e.g., example.com) require DNS providers that support CNAME flattening. Subdomains (e.g., meetings.example.com) work with all providers. - **Email links use linktime.io** -- Cancel/reschedule links in booking confirmation emails use the main linktime.io domain (required for authentication). - **Organization plan only** -- Custom domains are available exclusively on the Organization plan. - **Booking pages only** -- Your dashboard, settings, and sign-in pages remain on linktime.io. Visitors to auth-required paths on your custom domain are automatically redirected. --- # Trust & Engineering > How LinkTime is built -- 670+ automated tests, regular security audits, enterprise-grade encryption, and the engineering practices behind every booking. > Source: https://linktime.io/docs/enterprise/trust > Last updated: February 2026 LinkTime is engineered with the same rigor you'd expect from enterprise infrastructure. Every line of code is tested, every endpoint is authenticated, and every token is encrypted. | Metric | Value | Detail | |--------|-------|--------| | Automated Tests | 670+ | Unit + integration + E2E | | Database Models | 37 | Fully typed with Prisma | | API Endpoints | 152 | Every one authenticated & validated | | Integrations | 14 | Calendars, CRMs, video, payments | ## Testing Discipline Every feature ships with tests. Every deploy runs them. **670+** automated tests run on every commit. - **Unit tests** for business logic -- availability calculation, encryption, token refresh, CRM sync, workflow triggers - **Integration tests** for every API endpoint -- authentication, validation, error handling, edge cases - **E2E tests** for critical user flows -- booking creation, calendar sync, payment processing ```bash # Every deploy follows this $ npm run test:run # 670+ tests passed $ npm run build # TypeScript compilation clean $ prisma db push # Database schema synced $ railway up # Deployed ``` Code that fails tests does not ship. There are no overrides, no skip flags, no "we'll fix it later." ## Security Audit Program We don't just build features -- we regularly audit what we've built. LinkTime maintains a living audit document that tracks every security finding, performance issue, and technical debt item across the codebase. Each finding is categorized by severity, assigned a resolution target, and tracked to completion. | Severity | Found | Resolved | |----------|-------|----------| | Critical | 2 | 2 | | High | 2 | 2 | | Medium | 8 | 3 | | Low | 11 | 0 | > **All critical and high severity findings are resolved.** Medium and low findings are tracked with target versions and addressed in priority order during each release cycle. ## Encryption & Data Protection Your credentials are encrypted. Your data is validated. Your sessions are protected. ### At Rest - AES-256-GCM encryption for all OAuth tokens - SHA-256 hashed API keys (raw key never stored) - Encrypted database connections (SSL) ### In Transit - TLS everywhere -- no unencrypted traffic - Webhook signature verification (HMAC) - CSRF token protection on forms ### At the Edge - Edge middleware validates every request - Redis-based rate limiting on public endpoints - Zod validation on every API input ## Integration Reliability 14 integrations, all built with the same resilience patterns. ### Fire-and-Forget Architecture CRM sync, calendar creation, and notification delivery never block your core booking flow. If HubSpot is down, your booking still works. If Outlook is slow, your user still sees their confirmation. Integration errors are logged and retried -- never surfaced as booking failures. ### Proactive Token Management OAuth tokens are refreshed 5 minutes before expiry -- not when a request fails. If a token is permanently revoked, the integration auto-disconnects and notifies the user. No stale credentials, no silent failures. ### Connected Ecosystem | Integration | Category | |-------------|----------| | Google Calendar | Calendar | | Outlook | Calendar | | CalDAV / iCloud | Calendar | | Zoom | Video | | Google Meet | Video | | Microsoft Teams | Video | | HubSpot | CRM | | Salesforce | CRM | | Attio | CRM | | Stripe | Payments | | Zapier | Automation | | Make.com | Automation | ## Infrastructure Production-grade infrastructure with zero-downtime deployments. - **Railway** -- SOC 2 compliant hosting with automatic scaling - **PostgreSQL** -- Managed database with daily backups and point-in-time recovery - **Clerk** -- Enterprise authentication with OAuth 2.0 (Google + Microsoft) - **Upstash Redis** -- Distributed rate limiting across all instances - **Environment validation** -- Missing variables crash on startup, not on the first request - **Automated cron jobs** -- 7 background processes for reminders, cleanup, webhooks, and reconciliation - **Custom domains** -- Full DNS lifecycle management with automatic SSL - **Webhook retries** -- 6-attempt retry strategy with exponential backoff up to 24 hours ## Engineering Practices The standards behind every release. ### Type Safety TypeScript strict mode across the entire codebase. 37 Prisma models generate fully typed database queries. 40+ Zod schemas validate every external input before it touches business logic. ### Fail-Fast Philosophy Environment variables are validated at startup -- a missing secret crashes immediately rather than silently operating without protection. Required integrations are enforced. Optional ones degrade gracefully. ### Immutable Deploy Pipeline Every deploy follows the same pipeline: tests, type-check, database sync, deploy. The pipeline is a script, not a process someone might skip. If tests fail, the deploy doesn't happen. ### Defense in Depth Authentication at the edge (middleware), authorization in routes, validation on inputs, encryption on stored data, and signatures on webhooks. No single point of failure protects your data -- multiple layers do. ### Transparent Change History Every release includes a public changelog, a technical changelog, and a versioned audit document. Users always know what changed, and we always know what to review. ### Resilient Integrations External API calls use parallel execution with individual error isolation. Three CRMs can sync simultaneously -- if one fails, the others still succeed. Booking creation is never blocked by an integration failure. --- # Security > How LinkTime protects your data, credentials, and integrations. > Source: https://linktime.io/docs/enterprise/security > Last updated: February 2026 ## Overview LinkTime is built with security as a foundational principle. Your scheduling data, OAuth tokens, and integration credentials are protected by multiple layers of encryption, authentication, and verification. This page describes the security measures in place across the platform. > **The codebase is fundamentally solid** -- AES-256-GCM encryption, Clerk middleware, Zod validation, CSRF protection, rate limiting, webhook signatures (Stripe, Twilio), and timing-safe comparisons are all in place. We conduct regular security audits and address findings promptly. ## Encryption at Rest All sensitive credentials -- OAuth access tokens, refresh tokens, and API keys for your connected integrations -- are encrypted at rest using **AES-256-GCM** (authenticated encryption). This means even if the database were compromised, tokens would be unreadable without the encryption key. - Google, Outlook, Zoom, HubSpot, Attio, Salesforce, CalDAV, and Twilio tokens are all encrypted - Encryption key is a required environment variable -- the app will not start without it in production - GCM mode provides both confidentiality and integrity (tampered ciphertext is rejected) ## Authentication LinkTime uses **Clerk** for authentication, providing secure sign-in with Google and Microsoft OAuth. Authentication is enforced at the edge (middleware) before any route handler executes. - Edge middleware validates session tokens on every request to protected routes - API routes verify authentication before processing -- no unauthenticated data access - API keys for external access use SHA-256 hashing -- only the hash is stored, never the raw key ## Input Validation & CSRF Protection All user input is validated using **Zod schemas** before processing. This prevents injection attacks, malformed data, and unexpected behavior. - Booking forms, API endpoints, and webhook payloads all use strict Zod validation - CSRF token protection on booking form submissions prevents cross-site request forgery - SOQL injection prevention in Salesforce queries (single quotes escaped) ## Webhook Signature Verification All inbound webhooks are verified using cryptographic signatures to ensure they come from legitimate sources. | Provider | Method | Header | |----------|--------|--------| | Stripe | HMAC-SHA256 (timing-safe) | `stripe-signature` | | Twilio | HMAC-SHA1 (URL + params) | `x-twilio-signature` | | PayPal | Webhook ID verification | PayPal IPN verification | ## Rate Limiting Public-facing endpoints are protected by **Upstash Redis-based rate limiting** to prevent abuse and denial-of-service attacks. Rate limits are applied per-IP on booking creation and other high-value endpoints. ## Internal Endpoint Protection Cron jobs and internal endpoints are protected by a shared secret (Bearer token). The app requires this secret to be set in production -- it will not start without it. The secret is validated centrally with minimum length requirements. ## Integration Token Lifecycle OAuth tokens for connected integrations (Google, Outlook, HubSpot, Attio, Salesforce) are managed with proactive refresh and automatic disconnection: - **Proactive refresh:** Tokens are refreshed 5 minutes before expiry -- no failed requests from stale tokens - **Auto-disconnect:** If a token refresh fails (revoked or expired), all integration fields are cleared automatically - **Fire-and-forget:** CRM and calendar sync errors never block booking creation -- your core scheduling always works ## Infrastructure - **Hosting:** Railway (SOC 2 compliant infrastructure) - **Database:** PostgreSQL with encrypted connections (SSL) - **HTTPS:** All traffic encrypted in transit with TLS - **Environment variables:** Validated at startup -- missing required variables crash immediately (fail-fast) ## Reporting Vulnerabilities If you discover a security vulnerability, please email [security@linktime.io](mailto:security@linktime.io). We take all reports seriously and will respond promptly. --- # Audit Logs > A complete, searchable record of every action taken in your account -- built for compliance, security, and accountability. > Source: https://linktime.io/docs/enterprise/audit-logs > Last updated: February 2026 ## Overview LinkTime's audit log records every significant action in your account -- bookings created, settings changed, team members added, integrations connected, and more. This gives you a tamper-proof timeline of who did what and when, which is essential for: - **Compliance reporting** -- SOC 2, GDPR, and internal audit requirements - **Security investigations** -- trace suspicious activity back to a specific action and IP address - **Team accountability** -- see which admin changed a setting or removed a member - **Debugging** -- understand the sequence of events leading to unexpected behavior > **Zero impact on performance** -- Audit logging is fire-and-forget. It never blocks or slows down your bookings, API calls, or other operations. If the logging system has a transient error, your business logic continues unaffected. ## Accessing Your Audit Log Your audit log is available in the dashboard under **Settings > Audit Log**: 1. Go to your Dashboard 2. In the left sidebar, look under the **Settings** section 3. Click **Audit Log** (shield icon) The audit log page shows a chronological table of all actions, with the most recent events at the top. You can filter, search, and export the data from this page. ## What Gets Logged Every auditable action is logged with a timestamp, the acting user, and relevant context. Here is the complete list of tracked actions: | Category | Action | Display Label | |----------|--------|---------------| | Bookings | `booking.created` | Booking Created | | Bookings | `booking.cancelled` | Booking Cancelled | | Bookings | `booking.rescheduled` | Booking Rescheduled | | Event Types | `event_type.created` | Event Type Created | | Event Types | `event_type.updated` | Event Type Updated | | Event Types | `event_type.deleted` | Event Type Deleted | | Team & Organization | `team.created` | Team Created | | Team & Organization | `team.member_added` | Team Member Added | | Team & Organization | `team.member_removed` | Team Member Removed | | Team & Organization | `org.member_invited` | Member Invited | | Team & Organization | `org.member_removed` | Member Removed | | Team & Organization | `org.role_changed` | Role Changed | | Integrations | `integration.connected` | Integration Connected | | Integrations | `integration.disconnected` | Integration Disconnected | | Integrations | `api_key.created` | API Key Created | | Integrations | `api_key.deleted` | API Key Deleted | | Workflows | `workflow.created` | Workflow Created | | Workflows | `workflow.updated` | Workflow Updated | | Workflows | `workflow.deleted` | Workflow Deleted | | Voice Agents | `voice_agent.created` | Voice Agent Created | | Voice Agents | `voice_agent.updated` | Voice Agent Updated | | Voice Agents | `voice_agent.deleted` | Voice Agent Deleted | | Payments | `payment.received` | Payment Received | | Payments | `payment.refunded` | Payment Refunded | | Settings | `settings.updated` | Settings Updated | | Authentication | `auth.login` | Signed In | ## Understanding Log Details Each audit log entry contains structured details depending on the action type. This makes entries easy to read and search: **Booking Actions** -- Shows the invitee's email, event type name, and start time: ```json { "inviteeEmail": "client@acme.com", "eventType": "30 Minute Meeting", "startTime": "2026-02-15T14:00:00Z" } ``` **Integration Actions** -- Shows which service was connected or disconnected: ```json { "service": "google_calendar" } ``` **Team & Organization Actions** -- Shows the affected member, their role, and any role changes: ```json { "memberEmail": "jane@company.com", "role": "ADMIN", "previousRole": "MEMBER" } ``` **Settings Actions** -- Shows which setting was changed, with before/after values: ```json { "field": "timezone", "oldValue": "America/New_York", "newValue": "Europe/London" } ``` **Resource Actions (Events, Workflows, Voice Agents)** -- Shows the resource name: ```json { "name": "Discovery Call" } ``` ## Filtering & Searching The audit log supports two ways to narrow down entries: ### Category Filter Use the dropdown at the top of the audit log page to filter by category. Available categories: - All Actions - Bookings - Events - Team - Settings - Integrations - Workflows - Payments - Voice ### Text Search Type in the search box and press Enter to find entries matching a keyword. Search looks across the **action name**, **resource type**, and **resource ID**. For example, searching "zoom" would find integration connect/disconnect events for Zoom. ## CSV Export Click the **Export CSV** button to download your audit log as a CSV file. The export includes all entries matching your current filters (category and search). The CSV contains: - **Time** -- ISO 8601 timestamp of the action - **Action** -- Human-readable action label (e.g., "Booking Created") - **Resource** -- The type of resource affected (e.g., "booking", "event_type") - **Resource ID** -- The unique identifier of the affected resource - **Details** -- JSON-encoded context (invitee email, service name, etc.) - **IP Address** -- Masked or full depending on your plan The filename is auto-generated with the current date, e.g., `audit-log-2026-02-12.csv`. ## Retention by Plan Audit log retention varies by plan. You'll only see entries within your plan's retention window: | Plan | Retention | Use Case | |------|-----------|----------| | Free | 7 days | Recent activity awareness | | Pro | 90 days | Quarterly review and basic compliance | | Business | 365 days | Full annual audit trail for enterprise compliance | > **Note:** Entries older than your retention window are not deleted immediately -- they are simply hidden from view. A daily cleanup process permanently removes entries older than 365 days (the maximum retention for any plan). ## IP Address & Privacy LinkTime records the IP address associated with each action for security purposes. How the IP is displayed depends on your plan: | Plan | IP Display | Example | |------|-----------|---------| | Free / Pro | Masked (last octet hidden) | `192.168.1.xxx` | | Business | Full IP address | `192.168.1.42` | This aligns with the GDPR data minimization principle -- IP addresses are only fully visible when the plan specifically calls for full audit capabilities. For IPv6 addresses, the last segment is masked (e.g., `2001:db8::1234:xxxx`). ## Organization Audit Log If you're part of an organization, admins have access to a separate **organization-wide** audit log that shows actions from *all members*. This is essential for team oversight and compliance. - **Location:** Dashboard > Organization > Audit Log - **Access:** Only **OWNER** and **ADMIN** roles can view the org audit log - **User column:** Each entry shows the member who performed the action (name, email, avatar) - **Same features:** Category filters, text search, CSV export, and pagination all work the same as the personal audit log ## API Access You can query the audit log programmatically via the REST API. Authentication is required. ### Personal Audit Log ```bash # List audit logs with pagination GET /api/audit-logs?page=1&limit=50 # Filter by category GET /api/audit-logs?category=bookings # Text search GET /api/audit-logs?search=zoom # Export as CSV GET /api/audit-logs?export=csv # Combine filters GET /api/audit-logs?category=integrations&search=hubspot&page=1&limit=25 ``` ### Response Format ```json { "logs": [ { "id": "clx...", "action": "booking.created", "actionLabel": "Booking Created", "resource": "booking", "resourceId": "booking-abc123", "details": { "inviteeEmail": "client@acme.com", "eventType": "30 Minute Meeting" }, "ipAddress": "192.168.1.xxx", "createdAt": "2026-02-12T10:30:00.000Z" } ], "total": 142, "page": 1, "limit": 50, "hasMore": true, "retentionDays": 90 } ``` ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | number | 1 | Page number (1-indexed) | | `limit` | number | 50 | Results per page (max 100) | | `category` | string | all | Filter: all, bookings, events, team, settings, integrations, workflows, payments, voice | | `search` | string | -- | Text search across action, resource, and resource ID | | `export` | string | -- | Set to "csv" to download as CSV file | ### Organization Audit Log API ```bash # Requires OWNER or ADMIN role GET /api/organizations/{orgId}/audit-logs?page=1&limit=50 ``` Same query parameters as the personal audit log. Response includes an additional `user` object (name, email, image) on each log entry. ## Data Cleanup A daily automated process permanently deletes audit log entries older than 365 days. This provides defense-in-depth -- even if the API-level retention filter has an issue, the cleanup ensures no data persists beyond the maximum retention window. Each audit log entry stores the user's plan at the time the action occurred (`planAtTime`). This is a defense-in-depth measure -- it allows verification even if a user's plan changes later. ## FAQ ### Can audit logging slow down my bookings? No. Audit logging is fire-and-forget -- it runs in the background and never blocks or delays your business logic. Even if the audit system experiences a transient error, your bookings, API calls, and other operations continue normally. ### Can I delete specific audit log entries? No. Audit logs are append-only and cannot be selectively deleted. This is by design -- a tamper-proof audit trail is essential for compliance. Entries are automatically cleaned up after they exceed the maximum retention period (365 days). ### What happens when I upgrade or downgrade my plan? Your retention window adjusts immediately. Upgrading from Free to Pro instantly reveals up to 90 days of history (if entries exist). Downgrading hides older entries from view but does not delete them -- if you upgrade again, they reappear. ### Are webhook deliveries logged? Webhook deliveries themselves are not in the audit log -- they have their own delivery tracking in the Webhooks system. However, the actions that *trigger* webhooks (booking created, cancelled, etc.) are fully logged. --- # Page Analytics & Conversion Tracking > See exactly how your booking pages perform -- who visits, where they come from, and how many convert into actual bookings. > Source: https://linktime.io/docs/enterprise/analytics > Last updated: February 2026 ## Overview LinkTime automatically tracks views on your public booking pages (`linktime.io/yourname` and `linktime.io/yourname/event-slug`). Page Analytics turns this raw data into actionable insights: - **How many people** view your booking pages (total views & unique visitors) - **What percentage** of visitors actually book a meeting (conversion rate) - **Where visitors come from** (Google, LinkedIn, Twitter, direct, etc.) - **Which events convert best** -- per-event funnel showing views vs. bookings - **How metrics change over time** -- percent-change comparisons with the previous period > **Available on all plans.** Page Analytics works for everyone -- Free, Pro, and Business. Higher plans unlock longer analytics windows (see Date Presets & Plan Limits). ## Accessing Page Analytics Page Analytics lives on the same Analytics dashboard you already use for booking stats: 1. Go to your Analytics Dashboard 2. Click the **Page Views** tab (next to the Bookings tab) 3. Choose a date preset (7 days, 30 days, 90 days, or 1 year) The dashboard loads your stats, trend chart, conversion data, and referrer sources in parallel for fast rendering. ## Understanding Your Stats The top of the Page Views tab shows four stat cards. Each includes a percent-change comparison with the previous period (e.g., last 30 days vs. the 30 days before that): ### Page Views Total number of times your booking pages were loaded. This counts every visit, including repeat visitors. ### Unique Visitors Number of distinct sessions that viewed your pages. If the same person visits three times, they count as 1 unique visitor but 3 page views. ### Conversion Rate The percentage of unique visitors who completed a booking. Calculated as: ``` (Completed Bookings / Unique Visitors) x 100 ``` ### Top Referrer The domain that sent the most visitors to your booking pages. Common referrers include google.com, linkedin.com, twitter.com, and "Direct" (typed the URL directly or used a bookmark). ## Views Trend Chart Below the stat cards, an area chart shows your daily page views and unique visitors over the selected time period. This helps you spot patterns and measure the impact of promotions or link-sharing campaigns: - **Blue area** -- Total page views per day - **Green area** -- Unique visitors per day - **Hover** -- Shows exact numbers for any specific day A spike in views without a corresponding spike in bookings suggests visitors are finding your page but not converting -- check your event descriptions, availability, or pricing. ## Per-Event Conversion Funnels This is the most actionable section. The **Event Page Performance** table shows conversion data for *each individual event type*: | Column | What It Shows | |--------|---------------| | Event | The event type name and slug (links to the event page) | | Views | Total page views for that specific event's booking page | | Visitors | Unique visitors who viewed that event's page | | Bookings | Completed bookings for that event type | | Conv. Rate | Bookings / Unique Visitors x 100 | > **Why per-event rates matter more than the overall rate:** People who visit a specific event page (e.g., `/yourname/30min`) have clear booking intent. Their conversion rate is a more meaningful and actionable metric than the overall rate, which includes casual profile browsers. ## Top Referrers The referrers section shows where your visitors come from, sorted by volume. Each entry shows: - **Referrer domain** -- The website that linked to your booking page - **Visit count** -- How many times visitors came from that source - **Percentage** -- Share of total traffic from that referrer Common referrer values include `google.com`, `linkedin.com`, `twitter.com`, and `Direct` (no referrer -- the visitor typed your URL directly or used a bookmark). Understanding your traffic sources helps you invest in the channels that drive the most bookings. ## Event Performance Cards In addition to the analytics dashboard, each event type's edit page shows a small **performance summary card** with the last 30 days of data: 1. Go to Dashboard > Events 2. Click on any event type to open its edit page 3. The performance card appears between the header and the form, showing: **Views**, **Visitors**, **Bookings**, and **Conversion Rate** The card only appears when there is data to show (at least 1 view or 1 booking in the last 30 days). This gives you at-a-glance performance data exactly where you're editing the event. ## Date Presets & Plan Limits The Page Views tab offers four date presets. Your plan determines the maximum analytics window: | Preset | Free | Pro | Business | |--------|------|-----|----------| | Last 7 days | Yes | Yes | Yes | | Last 30 days | Yes | Yes | Yes | | Last 90 days | Limited to 30d | Yes | Yes | | Last year | Limited to 30d | Limited to 90d | Yes | If you select a preset longer than your plan allows, the data is automatically capped at your plan's maximum. A banner appears suggesting an upgrade for longer history. ## How Tracking Works Page Analytics uses LinkTime's existing page view tracking -- there's nothing new to install or configure: - **Automatic:** Every visit to your public booking pages is already tracked. Analytics queries this existing data. - **No cookies:** Tracking uses anonymous session IDs -- no cookie banners or consent dialogs needed. - **No scripts:** No external JavaScript tracking scripts are added to your pages. - **Retroactive:** Analytics works with all historical data -- you'll see stats from before this feature existed. - **Path-based:** Analytics identifies your pages by URL path pattern matching (`/yourname` and `/yourname/*`). ## Improving Your Conversion Rate A typical conversion rate for scheduling pages is 15-30%. If yours is lower, here are evidence-based ways to improve it: ### 1. Optimize Event Names Use clear, outcome-focused names. "Free Strategy Session" converts better than "30min-call". Visitors should immediately understand what they're booking. ### 2. Reduce Friction Check your availability windows -- if the next available slot is 2 weeks out, visitors leave. Open more time slots or reduce buffer times to show closer availability. ### 3. Add a Profile Photo Pages with a professional profile photo have higher trust and conversion. Make sure your profile is complete in Settings. ### 4. Check Your Top Referrers If most traffic comes from a specific channel, optimize your presence there. For example, if LinkedIn drives 60% of visitors, make sure your LinkedIn profile links directly to your most popular event type. ### 5. Compare Per-Event Rates Use the Event Page Performance table to identify underperformers. If "Discovery Call" converts at 25% but "Follow-Up" is at 5%, the follow-up page may need a better description or different duration. ## API Access Page analytics data is available via the REST API. Authentication is required. ### All Page Stats ```bash # Get full page analytics (default: last 30 days) GET /api/analytics/page-views # With date preset GET /api/analytics/page-views?preset=7d GET /api/analytics/page-views?preset=30d GET /api/analytics/page-views?preset=90d GET /api/analytics/page-views?preset=year ``` ### Response Format ```json { "stats": { "totalViews": 1250, "uniqueVisitors": 480 }, "trend": [ { "date": "2026-02-01", "views": 45, "uniqueVisitors": 32 }, { "date": "2026-02-02", "views": 38, "uniqueVisitors": 28 } ], "referrers": [ { "referrer": "google.com", "count": 180 }, { "referrer": "linkedin.com", "count": 95 }, { "referrer": "Direct", "count": 72 } ], "conversion": { "visitors": 480, "bookings": 62, "conversionRate": 12.9 }, "eventConversions": [ { "slug": "30min", "eventName": "30 Minute Meeting", "views": 500, "uniqueVisitors": 280, "bookings": 45, "conversionRate": 16.1 } ], "previousPeriod": { "totalViews": 1100, "uniqueVisitors": 420, "conversionRate": 11.4 }, "preset": "30d", "maxDays": 30 } ``` ### Single Event Stats ```bash # Get stats for a specific event type GET /api/analytics/page-views/30min ``` ### Single Event Response ```json { "totalViews": 500, "uniqueVisitors": 280, "bookings": 45, "conversionRate": 16.1 } ``` ## FAQ ### Do I need to install any tracking code? No. LinkTime tracks page views automatically on your public booking pages. There's no code to add, no cookies to manage, and no third-party scripts. It works immediately. ### Will I see historical data from before this feature launched? Yes. Page Analytics queries your existing page view data retroactively. If you've been using LinkTime for months, you'll see all that historical traffic data (within your plan's analytics window). ### What counts as a "unique visitor"? A unique visitor is a distinct session. If someone visits your page three times from the same browser in the same session, that counts as 1 unique visitor and 3 page views. Different devices or browsers count as separate visitors. ### Why is my conversion rate different on the dashboard vs. per-event? The dashboard shows your **overall conversion rate** (all visitors to any of your pages / all bookings). The per-event rate shows conversions for a **specific event type**. Per-event rates are typically higher because those visitors have clear booking intent -- they navigated to a specific event page, not just your profile. ### Does this track embedded booking widgets? Page views on embedded widgets are tracked separately. The Page Analytics dashboard focuses on your public booking pages at `linktime.io/yourname`. ### What's a good conversion rate? For scheduling pages, 15-30% is typical. Rates vary by industry, event type, and traffic source. The most meaningful comparison is your *own* rate over time -- use the percent-change indicators to track improvement.