Calendly integrates with Bolt.new in two ways: (1) embed the Calendly scheduling widget directly in React — zero API needed, works in the Bolt WebContainer preview immediately, or (2) use Calendly REST API v2 to build fully custom scheduling UIs. Webhook event handling (booking confirmations, cancellations) requires deploying to Netlify or Bolt Cloud first, since incoming webhooks cannot reach Bolt's browser-based runtime.
Adding Calendly Scheduling to Your Bolt.new App
Calendly is the most widely used scheduling automation tool, trusted by millions of businesses to eliminate the 'when are you free?' back-and-forth. For founders and product teams building with Bolt.new, Calendly integration enables booking flows directly inside their app — whether that is a SaaS product with a 'Schedule a demo' button, a consulting firm website with a booking widget on the contact page, or a coaching platform where clients book sessions inside the product dashboard.
Calendly offers two distinct integration paths with very different complexity levels. The first is the Embed Widget approach — Calendly provides a JavaScript library that renders a fully functional scheduling interface inside any web page. You load one script tag and create a div — that is literally the entire integration. No API key, no backend code, no webhook setup. This approach works in Bolt's WebContainer preview immediately and requires zero configuration. The trade-off is that the scheduling UI looks like Calendly (with Calendly's branding) rather than your own design.
The second path is the REST API v2, which lets you build completely custom scheduling interfaces. You can fetch the user's event types to build a custom booking flow, retrieve scheduled events to display a dashboard of upcoming meetings, access invitee details after a booking, and programmatically cancel or reschedule events. This requires a Calendly personal access token stored in a server-side API route. For responding to booking events in real time (sending confirmation emails, provisioning access, updating your database), Calendly sends webhook notifications to a URL you register — but this URL must be publicly accessible, meaning webhooks can only be tested after deploying. This guide covers both approaches so you can choose the right level of integration for your use case.
Integration method
Calendly offers two integration paths for Bolt.new apps. The embed approach uses Calendly's JavaScript widget script loaded in React — no API key required, works directly in the Bolt WebContainer preview. The REST API v2 approach uses a Next.js API route with a personal access token to fetch event types, scheduled events, and user data. Webhook event notifications (booking created, cancelled) require a deployed URL and cannot be received in Bolt's preview.
Prerequisites
- A Calendly account (calendly.com) — the free Basic plan is sufficient for the embed widget approach; the Standard plan ($10/month) is required for REST API access
- Your Calendly scheduling URL (e.g., calendly.com/your-username/event-type) for the widget embed approach
- A Calendly Personal Access Token from app.calendly.com/integrations/api_webhooks for the REST API approach (requires Standard plan or above)
- A Bolt.new project using Next.js (for the API route pattern) or any React framework (for the embed widget approach)
- A deployed URL on Netlify or Bolt Cloud for webhook testing — webhooks cannot be received in Bolt's WebContainer preview
Step-by-step guide
Embed the Calendly inline widget with zero API setup
Embed the Calendly inline widget with zero API setup
The fastest way to add Calendly scheduling to any Bolt.new app is the embed widget approach. Calendly provides a hosted JavaScript library that you load via a script tag, and a div element where the widget renders. This approach requires only your Calendly scheduling URL — no account API key, no backend code, no environment variables. It works in Bolt's WebContainer preview immediately and takes less than 5 minutes to implement. Calendly's embed widget loads as an external script from Calendly's CDN. In a React app, external scripts are typically loaded in the HTML head (index.html for Vite, or layout.tsx for Next.js) or dynamically created in a useEffect hook. The dynamic script loading approach in useEffect is cleaner for React because it avoids loading the Calendly script on pages that do not need it. The widget renders into a div element you create in your component. Calendly's API offers three widget styles: inline (renders directly in the page flow, best for dedicated booking pages or sections), popup widget (attaches a 'Schedule time with me' floating button at the bottom of the page), and popup text (wraps any link text to open the booking modal on click). The inline style is recommended for embedded app use. You can prefill user data in the widget URL to save your visitors from retyping information your app already has. Append query parameters to your Calendly URL: ?name=John%20Smith&email=john@example.com&a1=Custom%20Question%20Answer. If the logged-in user's name and email are available from your auth system, prefilling creates a significantly smoother booking experience. Customization is limited in the free embed approach — colors, fonts, and layout are controlled by Calendly's admin settings, not your code. For deeper branding, the only option is to upgrade to Calendly's Enterprise plan and use their Custom Embed with CSS customization. For most apps, the standard embed widget looks professional and functional.
Add a Calendly scheduling section to this page. Create a React component called CalendlyWidget that: (1) dynamically loads the Calendly widget script from https://assets.calendly.com/assets/external/widget.js in a useEffect hook, (2) renders a div with data-url='https://calendly.com/YOUR_USERNAME/30min' and style={{ minWidth: '320px', height: '700px' }}, (3) calls Calendly.initInlineWidget() to initialize the widget after the script loads. Add the calendly.css stylesheet link. Wrap this in a section with a heading 'Schedule a Call' and a brief description. Use TypeScript and handle the case where the widget script fails to load.
Paste this in Bolt.new chat
1// components/CalendlyWidget.tsx2import { useEffect, useRef } from 'react';34interface CalendlyWidgetProps {5 url: string;6 prefillName?: string;7 prefillEmail?: string;8 height?: number;9}1011export function CalendlyWidget({12 url,13 prefillName,14 prefillEmail,15 height = 700,16}: CalendlyWidgetProps) {17 const containerRef = useRef<HTMLDivElement>(null);1819 useEffect(() => {20 // Build URL with optional prefill parameters21 let widgetUrl = url;22 const params = new URLSearchParams();23 if (prefillName) params.set('name', prefillName);24 if (prefillEmail) params.set('email', prefillEmail);25 if (params.toString()) widgetUrl += '?' + params.toString();2627 // Load Calendly CSS28 const link = document.createElement('link');29 link.href = 'https://assets.calendly.com/assets/external/widget.css';30 link.rel = 'stylesheet';31 document.head.appendChild(link);3233 // Load Calendly widget script34 const script = document.createElement('script');35 script.src = 'https://assets.calendly.com/assets/external/widget.js';36 script.async = true;37 script.onload = () => {38 if (containerRef.current && (window as Window & { Calendly?: { initInlineWidget: (opts: object) => void } }).Calendly) {39 (window as Window & { Calendly: { initInlineWidget: (opts: object) => void } }).Calendly.initInlineWidget({40 url: widgetUrl,41 parentElement: containerRef.current,42 });43 }44 };45 document.head.appendChild(script);4647 return () => {48 document.head.removeChild(script);49 document.head.removeChild(link);50 };51 }, [url, prefillName, prefillEmail]);5253 return (54 <div55 ref={containerRef}56 style={{ minWidth: '320px', height: `${height}px` }}57 className="w-full rounded-lg overflow-hidden"58 />59 );60}Pro tip: Replace 'YOUR_USERNAME/30min' in the Calendly URL with your actual Calendly username and event type slug. Find your full scheduling URL in your Calendly dashboard under Event Types.
Expected result: The Calendly scheduling widget renders inside the Bolt preview. Visitors can see available time slots and complete a booking — the entire flow works in development without any API credentials or deployment.
Get a Calendly API token and set up the API route
Get a Calendly API token and set up the API route
For custom scheduling UIs that go beyond the embedded widget — displaying your own event type cards, showing upcoming bookings in a dashboard, or programmatically creating events — you need Calendly's REST API v2. This API requires authentication using a Personal Access Token, which is only available on Calendly's paid plans (Standard at $10/month and above). To get your Personal Access Token, log into your Calendly account and go to app.calendly.com/integrations/api_webhooks. Click 'Personal Access Tokens,' then 'Generate New Token.' Give it a descriptive name (e.g., 'My Bolt App - Development') and copy the token immediately — it is only shown once. Store it in your Bolt project's .env file as CALENDLY_ACCESS_TOKEN. Calendly's API v2 uses a 'current user' concept — most API calls first require fetching your user URI, which is a URL string like https://api.calendly.com/users/ABCD1234. Once you have the user URI, you can list event types, retrieve scheduled events for a date range, and access invitee details. The user URI is retrieved from the GET /users/me endpoint. Create a Next.js API route at app/api/calendly/route.ts that handles multiple Calendly API operations. This route acts as a secure proxy — your React frontend calls /api/calendly, which adds the secret access token and calls Calendly's API, then returns the results to the frontend. This keeps your Calendly token off the client side. Note that Calendly's API enforces rate limits of 600 requests per minute on Standard plans and higher on premium plans. For dashboard applications that fetch event data on page load, this limit is generous. For webhook-intensive applications, rate limits apply separately to webhook endpoints.
Create a Calendly API service for this Next.js app. Add CALENDLY_ACCESS_TOKEN to .env as a placeholder. Create an API route at app/api/calendly/event-types/route.ts that: (1) calls GET https://api.calendly.com/users/me with Authorization: Bearer {token} to get the current user URI, (2) calls GET https://api.calendly.com/event_types?user={userUri}&active=true to fetch active event types, (3) returns the list of event types with name, duration, scheduling_url, description, and color fields. Handle errors with appropriate HTTP status codes. Use TypeScript with proper type definitions for the Calendly API response.
Paste this in Bolt.new chat
1// app/api/calendly/event-types/route.ts2import { NextResponse } from 'next/server';34const CALENDLY_BASE = 'https://api.calendly.com';56interface CalendlyUser {7 resource: { uri: string; name: string; email: string };8}910interface CalendlyEventType {11 uri: string;12 name: string;13 active: boolean;14 scheduling_url: string;15 duration: number;16 description_plain: string;17 color: string;18 kind: string;19}2021interface CalendlyEventTypesResponse {22 collection: CalendlyEventType[];23}2425async function calendlyFetch<T>(endpoint: string, token: string): Promise<T> {26 const res = await fetch(`${CALENDLY_BASE}${endpoint}`, {27 headers: {28 Authorization: `Bearer ${token}`,29 'Content-Type': 'application/json',30 },31 });32 if (!res.ok) {33 throw new Error(`Calendly API error: ${res.status} ${res.statusText}`);34 }35 return res.json();36}3738export async function GET() {39 const token = process.env.CALENDLY_ACCESS_TOKEN;40 if (!token) {41 return NextResponse.json({ error: 'CALENDLY_ACCESS_TOKEN not configured' }, { status: 500 });42 }4344 try {45 // Step 1: Get current user's URI46 const user = await calendlyFetch<CalendlyUser>('/users/me', token);47 const userUri = user.resource.uri;4849 // Step 2: Fetch active event types for this user50 const eventTypesData = await calendlyFetch<CalendlyEventTypesResponse>(51 `/event_types?user=${encodeURIComponent(userUri)}&active=true`,52 token53 );5455 const eventTypes = eventTypesData.collection.map((et) => ({56 uri: et.uri,57 name: et.name,58 duration: et.duration,59 schedulingUrl: et.scheduling_url,60 description: et.description_plain,61 color: et.color,62 kind: et.kind,63 }));6465 return NextResponse.json({ eventTypes });66 } catch (error) {67 const message = error instanceof Error ? error.message : 'Unknown error';68 return NextResponse.json({ error: message }, { status: 500 });69 }70}Pro tip: Cache the user URI from GET /users/me in a module-level variable or a short-lived cache rather than fetching it on every request. The user URI almost never changes, so there is no need to hit the API for it more than once per server process startup.
Expected result: The API route at /api/calendly/event-types returns a list of your Calendly event types including names, durations, and scheduling URLs. The Bolt preview shows the event types rendered as styled booking cards.
Display event types as custom booking cards in React
Display event types as custom booking cards in React
With the API route returning your Calendly event types, the next step is building a custom scheduling interface in React that matches your app's design. Each event type becomes a card with the event name, duration, description, and a 'Book Now' button that links to Calendly's scheduling page for that specific event type. The React component fetches event types from your API route on mount using a useEffect hook. It handles loading, error, and success states. The 'Book Now' button for each card links to the event type's scheduling_url — when clicked, it opens Calendly's scheduling page in a new tab (or you can render the embedded widget for the selected event type inside a modal). For a more polished experience, combine both approaches: show custom event type cards that match your app's design, and when the user clicks 'Book,' render the Calendly embed widget in a modal overlay. This gives you full control over the pre-booking discovery phase (browsing event types) while using Calendly's reliable scheduling interface for the actual booking flow. You can also prefill user information in the scheduling URL. If your app has the current user's name and email (from your auth system), append them as URL parameters to the scheduling URL: ${schedulingUrl}?name=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}. This saves users from retyping information, improving completion rates for the booking flow.
Build a booking page that fetches event types from /api/calendly/event-types and displays them as custom cards. Each card should show: a colored left border using the event type's color field, the event name as heading, duration in minutes, description text, and a 'Book Now' button. When 'Book Now' is clicked, open a modal that renders a CalendlyWidget for that event type's scheduling URL, with the logged-in user's name and email prefilled. Show a loading skeleton while fetching. Handle errors with a retry button. Use Tailwind CSS and shadcn/ui Dialog for the modal.
Paste this in Bolt.new chat
1// app/booking/page.tsx2'use client';3import { useState, useEffect } from 'react';4import { CalendlyWidget } from '@/components/CalendlyWidget';5import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';67interface EventType {8 uri: string;9 name: string;10 duration: number;11 schedulingUrl: string;12 description: string;13 color: string;14}1516export default function BookingPage() {17 const [eventTypes, setEventTypes] = useState<EventType[]>([]);18 const [loading, setLoading] = useState(true);19 const [selectedEvent, setSelectedEvent] = useState<EventType | null>(null);2021 useEffect(() => {22 fetch('/api/calendly/event-types')23 .then((r) => r.json())24 .then((data) => { setEventTypes(data.eventTypes ?? []); setLoading(false); })25 .catch(() => setLoading(false));26 }, []);2728 if (loading) return <div className="p-8 text-center">Loading scheduling options...</div>;2930 return (31 <div className="mx-auto max-w-4xl p-8">32 <h1 className="mb-2 text-3xl font-bold">Schedule a Meeting</h1>33 <p className="mb-8 text-gray-500">Choose a time that works for you</p>34 <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">35 {eventTypes.map((et) => (36 <div37 key={et.uri}38 className="rounded-lg border p-5 shadow-sm hover:shadow-md transition-shadow"39 style={{ borderLeftWidth: '4px', borderLeftColor: et.color }}40 >41 <h2 className="font-semibold text-lg">{et.name}</h2>42 <p className="text-sm text-gray-500 mt-1">{et.duration} min</p>43 <p className="text-sm text-gray-600 mt-2 line-clamp-2">{et.description}</p>44 <button45 onClick={() => setSelectedEvent(et)}46 className="mt-4 w-full rounded bg-blue-600 py-2 text-sm font-medium text-white hover:bg-blue-700"47 >48 Book Now49 </button>50 </div>51 ))}52 </div>53 <Dialog open={!!selectedEvent} onOpenChange={() => setSelectedEvent(null)}>54 <DialogContent className="max-w-2xl">55 <DialogHeader>56 <DialogTitle>{selectedEvent?.name}</DialogTitle>57 </DialogHeader>58 {selectedEvent && (59 <CalendlyWidget url={selectedEvent.schedulingUrl} height={600} />60 )}61 </DialogContent>62 </Dialog>63 </div>64 );65}Pro tip: Calendly's event type color field is a hex color string like '#4a90d9'. Use it as the left border accent color on booking cards to visually match the color coding you set up in your Calendly account.
Expected result: The booking page shows your Calendly event types as styled cards. Clicking 'Book Now' opens a modal with the full scheduling widget for that event type. Users can complete a booking directly from your app's UI.
Handle Calendly webhooks after deploying to Netlify or Bolt Cloud
Handle Calendly webhooks after deploying to Netlify or Bolt Cloud
Calendly webhooks notify your app in real time when events occur: invitee.created (a booking is made), invitee.canceled (a booking is canceled), invitee_no_show.created, and routing_form_submission.created. These webhook notifications are critical for any app that needs to respond to bookings — sending confirmation emails, granting product access, updating CRM records, or notifying the host. Here is the critical limitation to understand: Bolt's WebContainer cannot receive incoming webhook requests. The WebContainer runs inside a browser tab with no public URL — Calendly has no way to send HTTP POST requests to your development environment. This is a fundamental constraint of all browser-based development environments. The standard workaround is to deploy first and then test webhooks on the live deployment. Before deploying, create the webhook handler in Bolt. The handler goes in a Next.js API route that accepts POST requests from Calendly. Calendly includes a webhook signing secret in the request headers (Calendly-Webhook-Signature header) using HMAC-SHA256, which you should verify to ensure the webhook genuinely came from Calendly and not a malicious actor. After deploying to Netlify or Bolt Cloud, register the webhook URL in your Calendly account: go to app.calendly.com/integrations/api_webhooks → Webhook Subscriptions → New Webhook Subscription. Enter your deployed URL (e.g., https://yourapp.netlify.app/api/webhooks/calendly), enter your signing key, select the event types you want, and click Create. Calendly will immediately send a test ping to verify your endpoint is reachable. Your handler should respond with 200 to pass the verification.
Create a Calendly webhook handler at app/api/webhooks/calendly/route.ts. The handler should: (1) Accept POST requests, (2) Read the Calendly-Webhook-Signature header and verify it using HMAC-SHA256 with CALENDLY_WEBHOOK_SIGNING_KEY from process.env (the signature is a timestamp+HMAC, format: 't=timestamp,v1=signature'), (3) Parse the JSON body to get the event type and payload, (4) Handle 'invitee.created' events by logging the invitee name, email, and event start time, (5) Handle 'invitee.canceled' events by logging the cancellation, (6) Return 200 for valid requests, 401 for invalid signatures. Add a comment explaining this endpoint only works after deployment since Bolt's WebContainer cannot receive incoming webhooks.
Paste this in Bolt.new chat
1// app/api/webhooks/calendly/route.ts2// NOTE: This webhook handler only works after deployment to Netlify or Bolt Cloud.3// Bolt's WebContainer has no public URL, so Calendly cannot reach it during development.4import { NextResponse } from 'next/server';5import crypto from 'crypto';67function verifyCalendlySignature(8 signingKey: string,9 signature: string,10 body: string11): boolean {12 // Signature format: t=timestamp,v1=hmac_signature13 const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')));14 const { t: timestamp, v1: hmacSig } = parts;15 if (!timestamp || !hmacSig) return false;1617 const payload = `${timestamp}.${body}`;18 const expectedSig = crypto19 .createHmac('sha256', signingKey)20 .update(payload)21 .digest('hex');2223 return crypto.timingSafeEqual(24 Buffer.from(hmacSig, 'hex'),25 Buffer.from(expectedSig, 'hex')26 );27}2829export async function POST(request: Request) {30 const signingKey = process.env.CALENDLY_WEBHOOK_SIGNING_KEY;31 if (!signingKey) {32 return NextResponse.json({ error: 'Signing key not configured' }, { status: 500 });33 }3435 const rawBody = await request.text();36 const signature = request.headers.get('Calendly-Webhook-Signature') ?? '';3738 if (!verifyCalendlySignature(signingKey, signature, rawBody)) {39 return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });40 }4142 const event = JSON.parse(rawBody);43 const eventType = event.event;44 const payload = event.payload;4546 if (eventType === 'invitee.created') {47 const { name, email } = payload.invitee;48 const startTime = payload.event?.start_time;49 console.log(`New booking: ${name} (${email}) at ${startTime}`);50 // TODO: Send confirmation email, update database, etc.51 } else if (eventType === 'invitee.canceled') {52 const { name, email } = payload.invitee;53 console.log(`Booking canceled: ${name} (${email})`);54 // TODO: Update database, send cancellation email55 }5657 return NextResponse.json({ received: true });58}Pro tip: After deploying, use Calendly's 'Send Test' button in the webhook subscription settings to verify your endpoint receives and processes events correctly. Check your server logs for the booking details after each test.
Expected result: After deploying to Netlify or Bolt Cloud and registering the webhook URL in Calendly, booking events trigger the webhook handler. Server logs show invitee names and booking times when test bookings are created in Calendly.
Common use cases
Book a Demo Button on a SaaS Landing Page
A 'Schedule a Demo' CTA on a marketing landing page that opens an inline Calendly widget when clicked, letting prospects book a 30-minute product demo directly on the page. Uses the embed widget approach — no API key required, no backend code, works in the Bolt preview instantly.
Add a 'Schedule a Demo' section to this landing page. When the user clicks the 'Book a Demo' button, show an inline Calendly widget in a modal. Load the Calendly widget script from https://assets.calendly.com/assets/external/widget.js and use Calendly.initInlineWidget() to render the widget in a div with my Calendly URL 'https://calendly.com/my-username/30min'. Make the modal dismissible and style the button with the brand primary color. Use Tailwind CSS.
Copy this prompt to try it in Bolt.new
Client Booking Dashboard with Custom UI
A React dashboard that fetches a consultant's event types from Calendly API v2 and displays them as styled cards with a book-now button for each. The custom UI matches the app's design system exactly rather than embedding Calendly's generic widget. Uses an API route for the Calendly token.
Build a booking dashboard that uses the Calendly API v2 to fetch my event types. Create a Next.js API route at /api/calendly/event-types that calls https://api.calendly.com/event_types with CALENDLY_ACCESS_TOKEN from process.env and returns the list. In the frontend, display each event type as a card showing the name, duration, and description, with a 'Book Now' button that links to the event type's scheduling URL. Use Tailwind CSS and shadcn/ui Card components.
Copy this prompt to try it in Bolt.new
Post-Booking Automation via Webhooks
When a user books an appointment through Calendly, automatically send them a welcome email, update their record in the database, and display a real-time confirmation in the app. This uses Calendly webhooks registered to the deployed app URL — must be configured after deploying to Netlify or Bolt Cloud.
Create a Calendly webhook handler at /api/webhooks/calendly that processes 'invitee.created' and 'invitee.canceled' events. When a booking is created, extract the invitee's name, email, and event start time from the webhook payload, save it to the database, and return 200. When canceled, update the booking record status to 'canceled'. Add CALENDLY_WEBHOOK_TOKEN to .env for signature verification. Note in a comment that this endpoint requires a deployed URL to receive events from Calendly.
Copy this prompt to try it in Bolt.new
Troubleshooting
The Calendly widget renders as a blank white box or shows a loading spinner that never finishes
Cause: The Calendly widget script may not have finished loading before initInlineWidget() was called, or the widget URL is incorrect. The div container may also be too small for the widget to render its initial view.
Solution: Ensure initInlineWidget() is called inside the script's onload handler, not immediately after appending the script tag. Verify the scheduling URL is your complete Calendly URL (including the event type path, not just your username). Set the container height to at least 630px — the Calendly widget requires this minimum height to render without scroll issues.
1// Ensure minimum height and script load order2<div3 ref={containerRef}4 style={{ minWidth: '320px', height: '700px' }} // minimum 630px height5 className="w-full"6/>7// And always call initInlineWidget in the script.onload callback:Calendly API returns 401 Unauthorized when calling /api/calendly endpoints
Cause: The CALENDLY_ACCESS_TOKEN in .env is missing, expired, or the token does not have sufficient permissions. Personal Access Tokens for the Calendly API require a Standard plan or above — they are not available on the free Basic plan.
Solution: Go to app.calendly.com/integrations/api_webhooks → Personal Access Tokens. Generate a new token and copy it immediately (tokens are only shown once). Update CALENDLY_ACCESS_TOKEN in your .env file. Verify your Calendly account is on the Standard plan or above — the REST API is a paid feature. Free accounts can only use the embed widget approach.
Calendly webhooks are not being received — the handler is never called
Cause: The app is still being tested in Bolt's WebContainer preview, which has no public URL for Calendly to send webhooks to. Calendly requires a publicly accessible HTTPS URL to deliver webhook events.
Solution: Deploy the app to Netlify or Bolt Cloud first to get a public URL. After deploying, go to app.calendly.com/integrations/api_webhooks → Webhook Subscriptions, update the webhook URL to your deployed endpoint (e.g., https://yourapp.netlify.app/api/webhooks/calendly), and click the 'Send Test' button to verify the connection. Set CALENDLY_WEBHOOK_SIGNING_KEY in your hosting platform's environment variables.
TypeError: window.Calendly is not defined when trying to initialize the widget
Cause: The Calendly widget script has not fully loaded when your code tries to call Calendly.initInlineWidget(). This can happen if the useEffect runs before the script's onload event fires, or if the component re-renders and calls initInlineWidget again before the script is ready.
Solution: Always check for window.Calendly before calling initInlineWidget, and call it only inside the script's onload callback. Add a typeof window !== 'undefined' guard at the top of the useEffect for Next.js SSR compatibility.
1// Always check before calling Calendly methods2script.onload = () => {3 if (typeof window !== 'undefined' && window.Calendly && containerRef.current) {4 window.Calendly.initInlineWidget({5 url: widgetUrl,6 parentElement: containerRef.current,7 });8 }9};Best practices
- Use the embed widget approach for simple 'Schedule a Call' buttons and contact pages — it requires zero API credentials and works in the Bolt WebContainer preview immediately
- Use the REST API approach only when you need to match your app's design system exactly or programmatically access booking data — the widget is faster to implement and more reliable
- Store CALENDLY_ACCESS_TOKEN in .env during development and in your hosting platform's environment variables for production — never expose it in client-side React code
- Prefill user name and email in the Calendly scheduling URL when the user is already logged in to your app — this significantly reduces friction and improves booking completion rates
- Always verify Calendly webhook signatures using HMAC-SHA256 before processing webhook events — this prevents malicious actors from triggering your booking logic with fake webhook calls
- Register your webhook URL after deploying to production, not during development — Calendly webhooks require a publicly accessible HTTPS URL that Bolt's WebContainer cannot provide
- Cache Calendly API responses (event types, user URI) with a short TTL (5-10 minutes) in your API route to reduce API calls and stay within Calendly's rate limits
Alternatives
Choose Acuity Scheduling (by Squarespace) if you need more customizable intake forms, client management features, and deeper booking workflow automation than Calendly offers.
Choose ScheduleOnce (by OnceHub) if you need enterprise-level team scheduling with routing rules, pooled availability, and CRM integrations beyond what Calendly's standard plans provide.
Choose Eventbrite if you are managing multi-attendee events, paid ticketing, or conferences rather than one-on-one or small-group scheduling meetings.
Use Zoom's scheduling API alongside Calendly if you need to automatically create Zoom meeting links as part of your booking confirmation flow.
Frequently asked questions
Does the Calendly embed widget work in Bolt.new's WebContainer preview?
Yes — the Calendly embed widget loads from Calendly's CDN and renders entirely in the browser. It requires no API key, no server-side code, and no deployment. You can see a fully functional scheduling widget in the Bolt preview as soon as you add the component. The only scheduling feature that requires deployment is webhook event handling.
Does Bolt.new work with Calendly's REST API?
Yes — Calendly's REST API v2 works in Bolt.new through a Next.js API route that proxies requests using your Personal Access Token. The API route runs server-side, reads the token from process.env.CALENDLY_ACCESS_TOKEN, calls Calendly's API endpoints, and returns data to your React components. This approach keeps the token secure and works in the Bolt WebContainer during development.
Can I receive Calendly webhook notifications in the Bolt.new preview?
No. Calendly webhooks require a publicly accessible HTTPS URL to deliver event notifications, and Bolt's WebContainer has no public URL. You must deploy your app to Netlify or Bolt Cloud first, then register your deployed webhook URL in the Calendly dashboard. Test webhooks by using Calendly's built-in 'Send Test' button in the webhook subscription settings after deployment.
Do I need a paid Calendly plan to integrate with Bolt.new?
The embed widget approach works with any Calendly plan, including the free Basic plan — you only need your Calendly scheduling URL. The REST API v2 (for fetching event types, scheduled events, and invitee data programmatically) requires the Standard plan at $10/month or higher. Webhook subscriptions also require a paid plan.
How do I deploy a Bolt.new app with Calendly webhooks to Netlify?
Use Bolt's Netlify integration under Settings → Applications, or push your code to GitHub and connect it to Netlify manually. After deploying, add CALENDLY_ACCESS_TOKEN and CALENDLY_WEBHOOK_SIGNING_KEY in your Netlify site's Environment Variables settings. Go to app.calendly.com/integrations/api_webhooks, create a new Webhook Subscription pointing to https://your-site.netlify.app/api/webhooks/calendly, and click Send Test to verify the connection.
Can I customize the Calendly widget's appearance to match my app's design?
Limited customization is available in the standard embed: you can set the background color, text color, and button color via query parameters on the widget URL (?background_color=ffffff&text_color=333333&primary_color=00a2ff). Deep CSS customization with custom fonts and full layout changes requires Calendly's Enterprise plan with Custom CSS support. For full design control, use the REST API approach to build a completely custom booking interface.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation