Set Up a Public Booking Page

Create event templates, availability slots, and an embeddable booking widget with email notifications and waitlist management.

intermediate18 min read

What You'll Build

The Lifestream Vault booking system gives you a fully featured appointment scheduler that lives alongside your documents. You configure event templates, define availability windows, and publish a booking page — all without any third-party scheduling service.

By the end of this guide you will have:

  • Event templates defining the types of meetings you offer (duration, location, description)
  • Availability slots specifying when guests can book with you
  • A public booking page with your own branding
  • An embeddable <lsv-booking> widget you can drop into any web page or React app
  • Email notifications sent to you and your guests automatically
  • Optional waitlist management for fully-booked slots (Business tier)
  • Optional booking analytics to track volume and conversion (Business tier)
Plan Required

Tier requirements

  • calendarBookingBasic — available on Pro tier: event templates, availability slots, booking page, email notifications, guest rescheduling
  • calendarBookingAdvanced — available on Business tier: waitlist management, booking analytics, team round-robin scheduling, custom branding

Upgrade in Settings → Subscription to access Business features.

Create Event Templates

An event template is a reusable definition of a meeting type. Each template specifies a name, duration, optional location (video call link, office address, or phone), and a description shown to guests on the booking page.

You can create multiple templates — for example: 15-minute intro call, 60-minute project review, 90-minute workshop session.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  process.env.VAULT_EMAIL!,
  process.env.VAULT_PASSWORD!,
);

const VAULT_ID = 'vault-uuid';

// Templates live on the 'booking' resource (there is no client.eventTemplates).
// Template-specific values (duration, location, color, ...) go in 'defaults'.
const introCall = await client.booking.createTemplate(VAULT_ID, {
  name: '30-Minute Intro Call',
  description:
    'A short introductory call to discuss your project goals and how Lifestream Vault can help.',
  defaults: {
    durationMin: 30,
    location: 'https://meet.example.com/intro',
    color: '#06b6d4',
  },
});

console.log('Template created:', introCall.id);

// Create a 60-minute deep-dive session
const deepDive = await client.booking.createTemplate(VAULT_ID, {
  name: '60-Minute Project Review',
  description:
    'An in-depth session to review project progress, roadblocks, and next milestones.',
  defaults: {
    durationMin: 60,
    location: 'https://meet.example.com/review',
    color: '#10b981',
  },
});

console.log('Deep-dive template created:', deepDive.id);

Set Up Availability Slots

Availability slots define windows of time when guests may book meetings. You can create recurring weekly patterns (e.g. Mondays 9 AM – 12 PM) or one-off time blocks for specific dates.

Slots are associated with a vault. When a guest books a slot, it is locked until the meeting concludes or you cancel it.

typescript
// Create a weekly availability slot — createSlot is positional: (vaultId, data)
// Monday–Friday, 9:00 AM – 5:00 PM, 30-minute bookings
const weekdaySlot = await client.booking.createSlot(VAULT_ID, {
  title: '30-Minute Intro Call',
  durationMin: 30,
  daysOfWeek: ['mon', 'tue', 'wed', 'thu', 'fri'],
  startTime: '09:00',
  endTime: '17:00',
  timezone: 'America/Toronto',
  bufferMin: 15, // 15-min buffer between back-to-back bookings
});

console.log('Slot created:', weekdaySlot.id);

// Create a second slot for longer sessions
const deepDiveSlot = await client.booking.createSlot(VAULT_ID, {
  title: '60-Minute Project Review',
  durationMin: 60,
  daysOfWeek: ['tue', 'thu'],
  startTime: '14:00',
  endTime: '16:00',
  timezone: 'America/Toronto',
});

console.log('Slot created:', deepDiveSlot.id);

All times are stored in UTC internally. Pass the timezone field in IANA format (e.g. America/Toronto, Europe/London) to ensure correct conversion. The guest-facing booking page displays times in the visitor's local timezone automatically.

Customize Your Booking Page

Your booking page inherits its branding from the PublishedVault configuration — the same record that powers your published vault site. You can set a logo URL, accent color, header text, and a short bio that appears at the top of the booking form.

Via the UI: open Settings → Publishing → Branding and fill in the fields, then click Save Branding.

You can customize the booking page with your brand colors, logo, and a custom welcome message. These branding options are available in Settings → Booking → Appearance and apply to both the hosted booking page and the embeddable widget.

typescript
// Update published-vault settings — booking branding fields are applied
// to the booking page. publishVault.update is positional: (vaultId, params)
await client.publishVault.update(VAULT_ID, {
  bookingLogoUrl: 'https://cdn.example.com/logo.svg',
  bookingAccentColor: '#06b6d4',
  bookingWelcomeMessage: 'Book time with the team — schedule a call to get started.',
  hidePoweredBy: true,
});

console.log('Branding updated.');
Plan Required

Custom branding (logo, accent color, custom header) is a Business tier feature. On the Pro tier, the booking page uses the Lifestream Vault default theme with your profile name and avatar.

Embed the Booking Widget

The <lsv-booking> custom element lets you embed a fully functional booking form on any web page — your marketing site, documentation portal, or a React/Vue/Svelte app — without an iframe.

Load the widget script from your Lifestream Vault instance, then drop the element anywhere on the page. The widget communicates directly with the API and handles timezone detection, slot rendering, and confirmation emails automatically.

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Book a Meeting</title>

  <!-- Load the booking widget from your Vault instance -->
  <script
    src="https://vault.lifestreamdynamics.com/api/v1/public/embed/booking.js"
    defer
  ></script>
</head>
<body>
  <h1>Schedule time with us</h1>

  <!--
    lsv-booking attributes:
      profile-slug  — required: your public profile slug
      vault-slug    — required: your vault slug
      api-url       — optional: API server base URL (defaults to widget origin)
      theme         — optional: "light" or "dark" (default: "light")
  -->
  <lsv-booking
    profile-slug="your-profile-slug"
    vault-slug="your-vault-slug"
    api-url="https://vault.lifestreamdynamics.com"
    theme="light"
  ></lsv-booking>
</body>
</html>

The widget script is served from /api/v1/public/embed/booking.js on your Vault instance and is publicly accessible without authentication. It registers the <lsv-booking> custom element and fetches available slots from the public booking API using the profile-slug and vault-slug attributes to identify the booking page. The theme attribute controls the color scheme ("light" or "dark").

Email Notifications

The booking system sends transactional emails automatically — no SMTP configuration is required on your part. Email delivery is handled by the email-delivery.worker BullMQ worker using your Vault instance's configured mail provider.

There are 5 email notification types in the booking system:

EventRecipientsDescription
Booking confirmedHost + GuestSent immediately when a guest completes a booking. Includes date, time, location, and a cancel link.
Booking cancelledHost + GuestSent when either party cancels. Includes cancellation reason if provided.
Booking rescheduledHost + GuestSent when a booking is moved to a new time. Includes old and new time.
Booking reminderGuestSent 24 hours before the scheduled time as a gentle reminder.
Waitlist joinedGuestSent when a guest joins the waitlist for a fully-booked slot. Includes their position in the queue.

Email content uses your vault's branding (logo, accent color) when Business-tier custom branding is enabled. Otherwise, emails use the Lifestream Vault default template.

To view email delivery history or re-queue a failed email, go to Settings → Email Log in the admin panel.

Guest Rescheduling

Guests can reschedule their bookings without contacting you directly. When a booking is confirmed, the system generates a rescheduleToken — a unique, short-lived token embedded in the confirmation email as a secure link.

Clicking the link takes the guest to a self-service rescheduling page where they can pick a new time from your available slots. The old slot is released and a new confirmation email is sent to both parties.

The rescheduleToken is stored on the booking record and rotates after each use. Tokens expire after 72 hours.

typescript
// Fetch a booking — getBooking is positional: (vaultId, bookingId)
const booking = await client.booking.getBooking(VAULT_ID, 'booking-uuid');

console.log('Booking status:', booking.status);
console.log('Guest email:', booking.guestEmail);

// Reschedule using the guest's reschedule token (from the confirmation email).
// rescheduleBooking is positional: (token, newStartAt)
await client.booking.rescheduleBooking(
  'guest-reschedule-token',
  '2026-03-20T14:00:00Z',
);

console.log('Booking rescheduled.');

Waitlist Management

When a slot is fully booked, the waitlist captures guest interest so you can fill cancellations automatically. Guests on the waitlist receive a notification when a spot opens up and have a limited window (default: 2 hours) to confirm the booking before the next guest is notified.

The waitlist is stored in the BookingWaitlist model and is processed by the booking service. Duplicate prevention ensures a guest can only join the waitlist once per slot.

typescript
// Guests join the waitlist through the PUBLIC booking page (no auth).
// joinWaitlist is positional: (profileSlug, vaultSlug, slotId, data)
const joined = await client.booking.joinWaitlist(
  'your-profile-slug',
  'your-vault-slug',
  'slot-uuid',
  {
    guestEmail: 'guest@example.com',
    guestName: 'Alex Kim',
    startAt: '2026-03-15T14:00:00Z',
  },
);

console.log('Waitlist position:', joined.position);
// Keep this token — it is how the guest later leaves the waitlist
console.log('Leave token:', joined.leaveToken);

// Host view: list the waitlist for a slot.
// getWaitlist is positional: (vaultId, slotId, params?) → { entries, total }
const waitlist = await client.booking.getWaitlist(VAULT_ID, 'slot-uuid');
console.log(`${waitlist.total} guests on the waitlist:`);

for (const entry of waitlist.entries) {
  console.log(` - Position ${entry.position}: ${entry.guestEmail}`);
}

// A guest can leave the waitlist using their leave token (GDPR withdrawal)
await client.booking.leaveWaitlist(joined.leaveToken);
console.log('Guest removed from the waitlist.');
Plan Required

Waitlist management is a Business tier feature (calendarBookingAdvanced). On Pro tier, fully-booked slots show a "sold out" message with no waitlist option. Upgrade in Settings → Subscription to enable waitlists.

Booking Analytics

The booking analytics resource gives you quantitative insight into how your scheduling page is performing. Three analytics views are available:

ViewWhat it shows
volumeTotal bookings, cancellations, and no-shows over a date range
funnelConversion from page view → slot selected → booking completed
peak-timesHeatmap of popular booking hours and days of the week

Analytics data is aggregated nightly by the usage-aggregation.worker.

typescript
// Booking analytics live on the 'booking' resource.
// getBookingAnalytics is positional: (vaultId, filters?)

// Volume analytics for February
const volume = await client.booking.getBookingAnalytics(VAULT_ID, {
  view: 'volume',
  from: '2026-02-01',
  to: '2026-02-28',
});

console.log('Total bookings:', volume.total);
console.log('Cancellations:', volume.cancelled);
console.log('Completion rate:', `${(volume.completionRate * 100).toFixed(1)}%`);

// Funnel analytics
const funnel = await client.booking.getBookingAnalytics(VAULT_ID, {
  view: 'funnel',
  from: '2026-02-01',
  to: '2026-02-28',
});

console.log('Page views:', funnel.pageViews);
console.log('Slot selections:', funnel.slotSelections);
console.log('Completed bookings:', funnel.completedBookings);
console.log('Funnel drop-off:', `${(funnel.dropOffRate * 100).toFixed(1)}%`);

// Peak-times heatmap
const peakTimes = await client.booking.getBookingAnalytics(VAULT_ID, {
  view: 'peak-times',
});

// Returns a 7×24 matrix of booking counts by day-of-week × hour
for (const row of peakTimes.heatmap) {
  console.log(`${row.dayOfWeek}: peak at ${row.peakHour}:00`);
}
Plan Required

Booking analytics require a Business tier subscription. Calling these endpoints on a Pro or Free account returns a 403 with the message "Booking analytics require Business tier".

Tips & Best Practices

Availability Patterns

  • Block off lunch: Create a daily availability gap from 12:00–13:00 so guests cannot book over your lunch hour. Use separate morning (09:00–12:00) and afternoon (13:00–17:00) slots for the same template.
  • Add buffer time: Set bufferMinutes: 15 on every slot to give yourself transition time between back-to-back meetings. This appears as unavailable time on the booking page.
  • Limit advance bookings: Use the maxAdvanceDays field (e.g. 30) to prevent guests from booking too far in the future, keeping your calendar manageable.

Timezone Handling

Availability slots are stored in UTC but displayed to guests in their detected local timezone. Always specify timezone in IANA format when creating slots — do not use UTC offset strings like +05:30 as they do not account for daylight saving time. Use date-fns-tz in integrations for safe timezone arithmetic.

Branding Consistency

Keep your booking page branding consistent with your marketing site. Use the same accent color, logo, and tagline across both. Guests who arrive from your website should feel a seamless transition into the booking flow.

Use Short Event Descriptions

The booking page shows the event template's description field below the duration. Keep it to 2–3 sentences that clearly set expectations: what the meeting covers, what the guest should prepare, and what happens after. Avoid jargon.

Test the Full Guest Flow

Before publishing your booking page, go through the complete guest flow:

  1. Open your public booking URL in an incognito window
  2. Select a slot and complete the booking form
  3. Verify the confirmation email arrives and the reschedule link works
  4. Cancel the test booking and verify the cancellation email

What's Next

Your booking page is live and ready to accept appointments. Here are some recommended next steps:

  • Calendar & Due Dates Guide — combine the booking system with project tracking via the activity heatmap and due-date alerts
  • Team booking groups are documented in the SDK Teams Reference.
  • Booking analytics are available in the vault dashboard under Analytics → Bookings.
  • Automate Your Vault — fire outbound HTTP events on booking.confirmed, booking.cancelled, and booking.rescheduled to integrate with your CRM or Slack
  • API Reference — full OpenAPI spec for all booking endpoints