Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Calendly

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.

What you'll learn

  • How to embed Calendly's inline scheduling widget in a React component with zero API key requirement
  • How to use Calendly REST API v2 to fetch event types and scheduled events via a server-side API route
  • How to handle Calendly webhook notifications for booking confirmations and cancellations after deployment
  • How to customize the Calendly embed widget with prefilled user data and color branding
  • Why Calendly webhooks require a deployed URL and how to test them after deploying to Netlify
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate19 min read20 minutesProductivityApril 2026RapidDev Engineering Team
TL;DR

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

Bolt Chat + API Route

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

1

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.

Bolt.new Prompt

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

components/CalendlyWidget.tsx
1// components/CalendlyWidget.tsx
2import { useEffect, useRef } from 'react';
3
4interface CalendlyWidgetProps {
5 url: string;
6 prefillName?: string;
7 prefillEmail?: string;
8 height?: number;
9}
10
11export function CalendlyWidget({
12 url,
13 prefillName,
14 prefillEmail,
15 height = 700,
16}: CalendlyWidgetProps) {
17 const containerRef = useRef<HTMLDivElement>(null);
18
19 useEffect(() => {
20 // Build URL with optional prefill parameters
21 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();
26
27 // Load Calendly CSS
28 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);
32
33 // Load Calendly widget script
34 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);
46
47 return () => {
48 document.head.removeChild(script);
49 document.head.removeChild(link);
50 };
51 }, [url, prefillName, prefillEmail]);
52
53 return (
54 <div
55 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.

2

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.

Bolt.new Prompt

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

app/api/calendly/event-types/route.ts
1// app/api/calendly/event-types/route.ts
2import { NextResponse } from 'next/server';
3
4const CALENDLY_BASE = 'https://api.calendly.com';
5
6interface CalendlyUser {
7 resource: { uri: string; name: string; email: string };
8}
9
10interface 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}
20
21interface CalendlyEventTypesResponse {
22 collection: CalendlyEventType[];
23}
24
25async 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}
37
38export 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 }
43
44 try {
45 // Step 1: Get current user's URI
46 const user = await calendlyFetch<CalendlyUser>('/users/me', token);
47 const userUri = user.resource.uri;
48
49 // Step 2: Fetch active event types for this user
50 const eventTypesData = await calendlyFetch<CalendlyEventTypesResponse>(
51 `/event_types?user=${encodeURIComponent(userUri)}&active=true`,
52 token
53 );
54
55 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 }));
64
65 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.

3

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.

Bolt.new Prompt

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

app/booking/page.tsx
1// app/booking/page.tsx
2'use client';
3import { useState, useEffect } from 'react';
4import { CalendlyWidget } from '@/components/CalendlyWidget';
5import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
6
7interface EventType {
8 uri: string;
9 name: string;
10 duration: number;
11 schedulingUrl: string;
12 description: string;
13 color: string;
14}
15
16export default function BookingPage() {
17 const [eventTypes, setEventTypes] = useState<EventType[]>([]);
18 const [loading, setLoading] = useState(true);
19 const [selectedEvent, setSelectedEvent] = useState<EventType | null>(null);
20
21 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 }, []);
27
28 if (loading) return <div className="p-8 text-center">Loading scheduling options...</div>;
29
30 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 <div
37 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 <button
45 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 Now
49 </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.

4

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.

Bolt.new Prompt

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

app/api/webhooks/calendly/route.ts
1// app/api/webhooks/calendly/route.ts
2// 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';
6
7function verifyCalendlySignature(
8 signingKey: string,
9 signature: string,
10 body: string
11): boolean {
12 // Signature format: t=timestamp,v1=hmac_signature
13 const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')));
14 const { t: timestamp, v1: hmacSig } = parts;
15 if (!timestamp || !hmacSig) return false;
16
17 const payload = `${timestamp}.${body}`;
18 const expectedSig = crypto
19 .createHmac('sha256', signingKey)
20 .update(payload)
21 .digest('hex');
22
23 return crypto.timingSafeEqual(
24 Buffer.from(hmacSig, 'hex'),
25 Buffer.from(expectedSig, 'hex')
26 );
27}
28
29export 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 }
34
35 const rawBody = await request.text();
36 const signature = request.headers.get('Calendly-Webhook-Signature') ?? '';
37
38 if (!verifyCalendlySignature(signingKey, signature, rawBody)) {
39 return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
40 }
41
42 const event = JSON.parse(rawBody);
43 const eventType = event.event;
44 const payload = event.payload;
45
46 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 email
55 }
56
57 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.

Bolt.new Prompt

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.

Bolt.new Prompt

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.

Bolt.new Prompt

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.

typescript
1// Ensure minimum height and script load order
2<div
3 ref={containerRef}
4 style={{ minWidth: '320px', height: '700px' }} // minimum 630px height
5 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.

typescript
1// Always check before calling Calendly methods
2script.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

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.