Integrate Bolt.new with ScheduleOnce (OnceHub) by embedding the JavaScript booking widget directly into your React app — this works in the WebContainer preview without deployment. For advanced API use (fetching bookings, managing pages, receiving webhook notifications for new appointments), create API routes and deploy to Netlify or Bolt Cloud first, since webhooks require a publicly accessible URL.
Adding Enterprise Scheduling to Bolt.new Apps with OnceHub
OnceHub (the company behind ScheduleOnce) is built for teams that need more than basic one-on-one scheduling. Where Calendly handles individual appointment booking, OnceHub handles the complex routing logic that sales teams need: round-robin distribution across team members, load balancing based on availability and capacity, multi-participant meetings, and deep integration with CRM workflows. If you are building a Bolt app for a sales team, customer success operation, or any team-based scheduling flow, OnceHub provides the infrastructure that would take weeks to build from scratch.
The simplest way to add scheduling to a Bolt app is through OnceHub's embeddable JavaScript widget. You include a script tag and a target element, and the full booking interface renders inside your app — complete with calendar, availability, time zone detection, and confirmation flows. This widget approach works inside Bolt's WebContainer preview, so you can build and test the full booking experience without deploying. The widget communicates directly with OnceHub's servers, so it requires no API keys or server-side code.
For more advanced use cases — syncing booking data to your own database, triggering workflows when appointments are created, or building custom dashboards that show booking metrics — you will use the OnceHub REST API. API access requires a paid OnceHub plan. API calls to fetch booking data work fine through Next.js API routes in both development and production. However, receiving webhook notifications (so OnceHub can push booking events to your app in real time) requires a deployed URL that OnceHub can reach, since Bolt's WebContainer cannot accept incoming HTTP connections from external services.
Integration method
OnceHub supports two integration paths in Bolt.new: embedding the JavaScript booking widget (which works directly in the preview) and using the REST API for programmatic scheduling management (which requires API routes and deployment for webhooks). The widget embed is the fastest way to add scheduling to a Bolt app, while the API unlocks automation — syncing bookings to your database, triggering follow-up emails, or updating CRM records when appointments are created.
Prerequisites
- An OnceHub account — free trial available at oncehub.com (API access requires a paid plan)
- At least one booking page configured in your OnceHub account with availability set
- An OnceHub API key from Settings → Integrations → API in your OnceHub dashboard (paid plans only)
- A deployed Bolt.new app on Netlify or Bolt Cloud (required for webhook endpoints — the embed widget works without deployment)
- A Next.js project in Bolt (prompt 'Create a Next.js app' to get started)
Step-by-step guide
Embed the OnceHub booking widget in your React component
Embed the OnceHub booking widget in your React component
The OnceHub JavaScript embed widget is the fastest path to scheduling in a Bolt app — and uniquely, it works directly in Bolt's WebContainer preview without any deployment. The widget is a third-party script that renders a full booking interface inside a container element you define. All availability logic, time zone detection, and confirmation flows run on OnceHub's servers, so no backend code is needed. To embed the widget, you need your OnceHub booking page URL. Log into your OnceHub account, navigate to Booking Pages, open the page you want to embed, and click 'Embed & Publish'. Copy the JavaScript embed code — it looks like a script tag that loads from cdn.oncehub.com followed by a div with a data-type attribute. In your Bolt app, prompt the AI to create a React component that loads this script. The recommended approach for React is to use a useEffect hook to inject the script tag dynamically (to avoid SSR issues) and a div with the target ID that OnceHub's script will populate. Apply CSS to set a minimum height on the container so the widget has space to render — OnceHub recommends at least 700px for the full calendar view. Because the embed widget communicates directly with OnceHub's CDN, it fully bypasses Bolt's WebContainer networking limitations. You will see the live booking interface in the preview pane, and visitors can complete real bookings through it without any additional configuration. This makes it the ideal starting point before adding any API-based automation.
Create a React component called SchedulingWidget that embeds an OnceHub booking page. Use useEffect to dynamically inject the OnceHub embed script from cdn.oncehub.com. Add a target div with id='SOIDIV_your-booking-page-id' and the required data attributes (data-type='bookingpage', data-bookingpage='your-booking-page-id', data-color='#1a73e8', data-height='700'). Style the container with a minimum height of 700px and a loading state while the script initializes. Replace 'your-booking-page-id' with a prop so the component is reusable.
Paste this in Bolt.new chat
1// components/SchedulingWidget.tsx2'use client';34import { useEffect, useRef } from 'react';56interface SchedulingWidgetProps {7 bookingPageId: string;8 height?: number;9 color?: string;10}1112export default function SchedulingWidget({13 bookingPageId,14 height = 700,15 color = '#1a73e8',16}: SchedulingWidgetProps) {17 const containerRef = useRef<HTMLDivElement>(null);18 const scriptLoaded = useRef(false);1920 useEffect(() => {21 if (scriptLoaded.current) return;22 scriptLoaded.current = true;2324 const script = document.createElement('script');25 script.src = 'https://cdn.oncehub.com/mergedjs/so.js';26 script.async = true;27 document.body.appendChild(script);2829 return () => {30 // Cleanup on unmount31 const existingScript = document.querySelector(32 'script[src="https://cdn.oncehub.com/mergedjs/so.js"]'33 );34 if (existingScript) document.body.removeChild(existingScript);35 };36 }, []);3738 return (39 <div40 id={`SOIDIV_${bookingPageId}`}41 data-type="bookingpage"42 data-bookingpage={bookingPageId}43 data-color={color}44 data-height={String(height)}45 ref={containerRef}46 style={{ minHeight: `${height}px`, width: '100%' }}47 />48 );49}Pro tip: Find your booking page ID in the OnceHub dashboard — it is the slug in the URL of your booking page (e.g., for oncehub.com/yourcompany/demo-call, the ID is 'yourcompany/demo-call'). The embed widget is free to use on all OnceHub plans including the trial.
Expected result: Your Bolt app preview shows the OnceHub booking calendar widget rendered inline, with available time slots visible. Visitors can select a time and complete a booking without leaving the page.
Set up OnceHub API authentication and fetch bookings
Set up OnceHub API authentication and fetch bookings
The OnceHub REST API uses API key authentication. Every request includes your API key in the Authorization header as a Bearer token. This is simpler than OAuth-based APIs — no token exchange, no callback URLs, no expiry handling. However, because your API key is a secret credential, all API calls must go through a server-side Next.js API route, never from client-side React components. Get your OnceHub API key by logging into your OnceHub account, navigating to Settings, then Integrations, then the API section. Generate a new API key and copy it — you will only see it once. Store it in your project's .env.local file as ONCEHUB_API_KEY. The OnceHub REST API base URL is https://api.oncehub.com/v2. The most useful endpoints for Bolt integrations are: GET /bookings for fetching scheduled appointments (supports date range filtering and pagination), GET /booking-pages for retrieving your configured booking pages, and GET /users for listing team members. The API returns JSON with consistent envelope patterns — most list endpoints return a data array plus pagination metadata. When fetching bookings, use the start_time and end_time query parameters to scope results to a meaningful date range. Fetching all bookings without a date filter on an active account can return thousands of records. The API supports pagination via limit (max 100) and offset parameters. For a dashboard showing today's and tomorrow's appointments, filter to a 48-hour window to keep response times fast and result sets manageable.
Create a Next.js API route at /api/oncehub/bookings that fetches upcoming bookings from the OnceHub REST API. Use the ONCEHUB_API_KEY from environment variables as a Bearer token. Fetch bookings for the next 7 days by passing start_time and end_time as ISO 8601 timestamps. Return the booking ID, customer name, email, start time, end time, booking page name, and assigned team member. Handle API errors gracefully.
Paste this in Bolt.new chat
1// app/api/oncehub/bookings/route.ts2import { NextRequest, NextResponse } from 'next/server';34const ONCEHUB_API_BASE = 'https://api.oncehub.com/v2';56export async function GET(request: NextRequest) {7 const apiKey = process.env.ONCEHUB_API_KEY;8 if (!apiKey) {9 return NextResponse.json({ error: 'OnceHub API key not configured' }, { status: 500 });10 }1112 const { searchParams } = new URL(request.url);13 const daysAhead = parseInt(searchParams.get('days') ?? '7', 10);1415 const now = new Date();16 const future = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);1718 const queryParams = new URLSearchParams({19 start_time: now.toISOString(),20 end_time: future.toISOString(),21 limit: '100',22 status: 'booked',23 });2425 const response = await fetch(26 `${ONCEHUB_API_BASE}/bookings?${queryParams.toString()}`,27 {28 headers: {29 Authorization: `Bearer ${apiKey}`,30 'Content-Type': 'application/json',31 },32 }33 );3435 if (!response.ok) {36 const error = await response.json().catch(() => ({}));37 return NextResponse.json(38 { error: error.message ?? `OnceHub API error ${response.status}` },39 { status: response.status }40 );41 }4243 const data = await response.json();4445 const bookings = (data.data ?? []).map((booking: Record<string, unknown>) => ({46 id: booking.id,47 customerName: (booking.customer as Record<string, unknown>)?.name,48 customerEmail: (booking.customer as Record<string, unknown>)?.email,49 startTime: booking.start_time,50 endTime: booking.end_time,51 bookingPageName: (booking.booking_page as Record<string, unknown>)?.name,52 assignedTo: (booking.assigned_member as Record<string, unknown>)?.name,53 meetingLink: booking.join_url,54 status: booking.status,55 }));5657 return NextResponse.json({58 bookings,59 total: data.total_records ?? bookings.length,60 pageSize: data.paging?.limit ?? bookings.length,61 });62}Pro tip: OnceHub API keys have no expiry, but they inherit the permissions of the account that generated them. If you are building a multi-user app where each customer has their own OnceHub account, you will need OAuth 2.0 instead of API key auth — contact OnceHub about their partner API program.
Expected result: Calling /api/oncehub/bookings returns a JSON array of upcoming appointments with customer details, times, and assigned team members from your OnceHub account.
Configure webhook notifications for real-time booking events
Configure webhook notifications for real-time booking events
Webhooks allow OnceHub to push notifications to your app the moment a booking is created, rescheduled, or cancelled — without your app polling the API every few minutes. This is the right approach for triggering downstream workflows: saving bookings to your database, sending custom confirmation emails, creating Zoom meetings, or updating a CRM record. Important architectural note: OnceHub webhooks are incoming HTTP requests from OnceHub's servers to your app. Bolt's WebContainer cannot receive incoming connections from external services — it runs entirely inside a browser tab. This means you must deploy your app to Netlify or Bolt Cloud before testing webhooks. The WebContainer limitation is fundamental to how Bolt works; there is no workaround during development. Once deployed, your webhook endpoint receives events reliably. To configure a webhook: in your OnceHub account, go to Settings → Integrations → Webhooks (or API → Webhooks depending on your plan tier). Click Add Webhook, enter your deployed app's URL for the endpoint (e.g., https://your-app.netlify.app/api/oncehub/webhook), select the event types you want (at minimum: booking.created, booking.cancelled, booking.rescheduled), and optionally set a shared secret for payload validation. The shared secret adds security — OnceHub includes an HMAC-SHA256 signature of the payload in the X-OnceHub-Signature header. Your webhook handler should verify this signature before processing the event. This prevents bad actors from sending fake booking events to your endpoint. Store the shared secret in your .env as ONCEHUB_WEBHOOK_SECRET and access it via process.env. Always respond to OnceHub webhooks with a 200 status code as quickly as possible — before doing any database writes or API calls. If your handler takes too long (OnceHub's timeout is 30 seconds), OnceHub marks the delivery as failed and retries. Use a queue or process the heavy work asynchronously after returning 200.
Create a webhook handler at /api/oncehub/webhook for OnceHub booking notifications. It should receive POST requests, verify the X-OnceHub-Signature header using HMAC-SHA256 with ONCEHUB_WEBHOOK_SECRET from .env, parse the JSON body, and handle three event types: booking.created (log and save to database), booking.cancelled (update status), and booking.rescheduled (update time). Return 200 immediately and process asynchronously. Add TypeScript types for the OnceHub webhook payload.
Paste this in Bolt.new chat
1// app/api/oncehub/webhook/route.ts2import { NextRequest, NextResponse } from 'next/server';3import crypto from 'crypto';45interface OnceHubWebhookPayload {6 event_type: 'booking.created' | 'booking.cancelled' | 'booking.rescheduled';7 booking: {8 id: string;9 status: string;10 start_time: string;11 end_time: string;12 customer: {13 name: string;14 email: string;15 phone?: string;16 };17 booking_page: {18 id: string;19 name: string;20 };21 assigned_member?: {22 id: string;23 name: string;24 email: string;25 };26 join_url?: string;27 };28}2930function verifySignature(payload: string, signature: string, secret: string): boolean {31 const expectedSig = crypto32 .createHmac('sha256', secret)33 .update(payload, 'utf8')34 .digest('hex');35 return crypto.timingSafeEqual(36 Buffer.from(signature, 'hex'),37 Buffer.from(expectedSig, 'hex')38 );39}4041export async function POST(request: NextRequest) {42 const rawBody = await request.text();43 const signature = request.headers.get('x-oncehub-signature') ?? '';44 const webhookSecret = process.env.ONCEHUB_WEBHOOK_SECRET ?? '';4546 if (webhookSecret && signature) {47 const isValid = verifySignature(rawBody, signature, webhookSecret);48 if (!isValid) {49 return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });50 }51 }5253 // Return 200 immediately — process async54 const payload: OnceHubWebhookPayload = JSON.parse(rawBody);5556 // Fire-and-forget processing57 processWebhookEvent(payload).catch(console.error);5859 return NextResponse.json({ received: true });60}6162async function processWebhookEvent(payload: OnceHubWebhookPayload) {63 const { event_type, booking } = payload;6465 switch (event_type) {66 case 'booking.created':67 console.log('New booking:', booking.id, booking.customer.email, booking.start_time);68 // Save to your database here69 break;70 case 'booking.cancelled':71 console.log('Booking cancelled:', booking.id);72 // Update status in your database73 break;74 case 'booking.rescheduled':75 console.log('Booking rescheduled:', booking.id, 'New time:', booking.start_time);76 // Update time in your database77 break;78 default:79 console.log('Unknown event type:', event_type);80 }81}Pro tip: During development, use ngrok or a similar tunneling tool to expose your local server temporarily and test webhooks before deploying. Run ngrok http 3000, copy the HTTPS URL, register it as your OnceHub webhook endpoint, and you can test the full flow locally without deploying.
Expected result: After deploying and registering the webhook URL in OnceHub, creating a test booking in OnceHub sends a POST to your deployed endpoint. The server logs show the booking details and respond with 200 status, confirming the webhook pipeline works end-to-end.
Build a booking management dashboard UI
Build a booking management dashboard UI
With the API routes in place, build a React dashboard that displays upcoming bookings grouped by team member. This is a common use case for sales and customer success teams — a manager view showing who has what scheduled, without requiring everyone to log into OnceHub directly. The dashboard should fetch from /api/oncehub/bookings on mount, display a loading skeleton while data loads, and render bookings in a card or table format. Group bookings by assigned_member name using a reduce or groupBy operation. Show the meeting time in the user's local time zone using JavaScript's Intl.DateTimeFormat API — OnceHub returns times in UTC (ISO 8601), and your users will expect to see their local time. Add a date range selector (today, this week, next 7 days) that triggers a new API call with the appropriate days parameter. A simple set of buttons or a select dropdown is sufficient — full date picker libraries add complexity without much value for this use case. For the booking cards, show the customer name, email, scheduled time (formatted), meeting type (booking page name), assigned team member, and a link to join the meeting if a join_url is available. Color-code by status: blue for upcoming, grey for completed, red for cancelled. This gives managers a fast visual scan of team utilization. This entire UI component works in Bolt's WebContainer preview. The API route calls OnceHub outbound, which the WebContainer allows. Only the webhook handler requires deployment.
Build a BookingDashboard React component that fetches from /api/oncehub/bookings with a configurable days parameter (default: 7). Show a loading skeleton while fetching. Group returned bookings by assigned team member and display them in a clean card layout. Each booking card shows: customer name, customer email, formatted start time (user's local time zone), meeting type, and a 'Join Meeting' button if a join URL exists. Add Today / This Week / Next 14 Days filter buttons at the top. Show total booking count.
Paste this in Bolt.new chat
1// components/BookingDashboard.tsx2'use client';34import { useEffect, useState } from 'react';56interface Booking {7 id: string;8 customerName: string;9 customerEmail: string;10 startTime: string;11 endTime: string;12 bookingPageName: string;13 assignedTo: string;14 meetingLink?: string;15 status: string;16}1718function formatTime(isoString: string): string {19 return new Intl.DateTimeFormat(undefined, {20 weekday: 'short',21 month: 'short',22 day: 'numeric',23 hour: 'numeric',24 minute: '2-digit',25 timeZoneName: 'short',26 }).format(new Date(isoString));27}2829export default function BookingDashboard() {30 const [bookings, setBookings] = useState<Booking[]>([]);31 const [loading, setLoading] = useState(true);32 const [days, setDays] = useState(7);33 const [total, setTotal] = useState(0);3435 useEffect(() => {36 setLoading(true);37 fetch(`/api/oncehub/bookings?days=${days}`)38 .then((r) => r.json())39 .then((data) => {40 setBookings(data.bookings ?? []);41 setTotal(data.total ?? 0);42 })43 .catch(console.error)44 .finally(() => setLoading(false));45 }, [days]);4647 const grouped = bookings.reduce<Record<string, Booking[]>>((acc, b) => {48 const key = b.assignedTo ?? 'Unassigned';49 if (!acc[key]) acc[key] = [];50 acc[key].push(b);51 return acc;52 }, {});5354 return (55 <div className="p-6 max-w-5xl mx-auto">56 <div className="flex items-center justify-between mb-6">57 <h1 className="text-2xl font-bold">Upcoming Bookings ({total})</h1>58 <div className="flex gap-2">59 {[1, 7, 14].map((d) => (60 <button61 key={d}62 onClick={() => setDays(d)}63 className={`px-4 py-2 rounded text-sm ${64 days === d ? 'bg-blue-600 text-white' : 'bg-gray-100 hover:bg-gray-200'65 }`}66 >67 {d === 1 ? 'Today' : d === 7 ? 'This Week' : 'Next 14 Days'}68 </button>69 ))}70 </div>71 </div>7273 {loading ? (74 <div className="space-y-4">75 {[1, 2, 3].map((i) => (76 <div key={i} className="h-24 bg-gray-100 rounded animate-pulse" />77 ))}78 </div>79 ) : Object.keys(grouped).length === 0 ? (80 <p className="text-gray-500 text-center py-12">No bookings in this period.</p>81 ) : (82 Object.entries(grouped).map(([member, memberBookings]) => (83 <div key={member} className="mb-8">84 <h2 className="text-lg font-semibold text-gray-700 mb-3">{member}</h2>85 <div className="space-y-3">86 {memberBookings.map((b) => (87 <div key={b.id} className="border rounded-lg p-4 flex justify-between items-start">88 <div>89 <p className="font-medium">{b.customerName}</p>90 <p className="text-sm text-gray-500">{b.customerEmail}</p>91 <p className="text-sm text-blue-600 mt-1">{formatTime(b.startTime)}</p>92 <p className="text-xs text-gray-400">{b.bookingPageName}</p>93 </div>94 {b.meetingLink && (95 <a96 href={b.meetingLink}97 target="_blank"98 rel="noopener noreferrer"99 className="px-3 py-1 bg-blue-50 text-blue-700 rounded text-sm hover:bg-blue-100"100 >101 Join102 </a>103 )}104 </div>105 ))}106 </div>107 </div>108 ))109 )}110 </div>111 );112}Pro tip: OnceHub's API returns booking times in UTC. The formatTime function above uses Intl.DateTimeFormat with no explicit timeZone, which defaults to the browser's local time zone — exactly what you want for a team dashboard viewed in a single office. For a global team, add a time zone selector.
Expected result: The Bolt preview shows a booking dashboard with groupings by team member, formatted meeting times in local time zone, and filter buttons for different date ranges. The data comes live from your OnceHub account.
Common use cases
Sales demo booking widget inside a product page
Embed an OnceHub booking page directly into a product landing page built with Bolt. Visitors can schedule a demo without leaving the page. OnceHub handles round-robin routing to the next available sales rep automatically.
Add an OnceHub booking widget to my React landing page. The widget should appear in a dedicated section below the hero. Use the OnceHub embed script with my booking page URL and render it inside a styled container div with a heading 'Schedule a Demo'. The embed should load asynchronously and not block the page render.
Copy this prompt to try it in Bolt.new
Customer success appointment dashboard
Build an internal dashboard that fetches all upcoming appointments from the OnceHub API and displays them grouped by team member, with the meeting type, customer name, and scheduled time. Customer success managers can see their team's schedule at a glance without logging into OnceHub.
Create a Next.js app with an OnceHub integration. Build a scheduling dashboard that fetches all upcoming bookings from the OnceHub API using an API key stored in .env. Display bookings in a table grouped by assigned team member, showing the event type, customer name, start time, and meeting link. Add a date range filter.
Copy this prompt to try it in Bolt.new
Booking confirmation webhook handler
When a new booking is created in OnceHub, automatically save the booking details to your database and send a custom confirmation message via your preferred email provider. This is useful when you need booking data in your own system for CRM syncing, billing, or custom follow-up logic.
Add a webhook handler at /api/oncehub/webhook that receives OnceHub booking notifications. When a new booking event arrives (event_type: BOOKED), save the customer name, email, scheduled time, and booking ID to my database. Validate the webhook using the shared secret from my .env file. Return 200 immediately and process async.
Copy this prompt to try it in Bolt.new
Troubleshooting
The OnceHub booking widget does not render — container stays blank or shows a grey box
Cause: The booking page ID is incorrect, the OnceHub script failed to load due to a network issue, or the container element's ID does not match the expected SOIDIV_ pattern that OnceHub's script looks for.
Solution: Open browser DevTools and check the Console for script errors and the Network tab to confirm so.js loaded successfully (status 200). Verify the div ID exactly matches SOIDIV_{your-booking-page-id} — the prefix is required. Check that your booking page is published and active in the OnceHub dashboard (draft pages will not render). Also confirm the data-bookingpage attribute matches your page's slug exactly, including any path separators.
API calls return 401 Unauthorized despite adding the ONCEHUB_API_KEY to .env
Cause: The API key is not being read from the environment variable correctly, the key was generated on a free/trial plan that does not have API access, or the key was revoked in the OnceHub dashboard.
Solution: Confirm that ONCEHUB_API_KEY is set in .env.local (for local dev) and in your hosting platform's environment variables (for production). Restart the Next.js dev server after adding the variable. Log the first 8 characters of the key server-side to confirm it is loading. Verify in OnceHub Settings → Integrations → API that the key is still active and that your plan includes API access — OnceHub requires a paid plan for REST API usage.
1// Add to your API route temporarily to debug:2console.log('API key loaded:', process.env.ONCEHUB_API_KEY?.substring(0, 8));Webhook endpoint returns 200 in testing but OnceHub marks deliveries as failed in production
Cause: The webhook handler is performing synchronous database operations that exceed OnceHub's 30-second delivery timeout, or the deployed endpoint URL is not publicly accessible.
Solution: Ensure your webhook handler returns NextResponse.json({ received: true }) immediately and processes all database writes and downstream API calls asynchronously using Promise chains or a background queue. Verify the deployed URL is accessible by visiting it in a browser — it should return a 405 Method Not Allowed (GET is not allowed, but the route exists). Check your hosting platform's function logs for any errors during webhook processing.
1// Always return 200 before processing:2export async function POST(request: NextRequest) {3 const payload = await request.json();4 // Return FIRST, then process5 processAsync(payload).catch(console.error);6 return NextResponse.json({ received: true }); // Immediate 2007}Bookings from the API are missing expected fields like assigned_member or join_url
Cause: The booking was created before the assigned team member was set up, the meeting type does not include video conferencing, or the API key's account does not have access to view team member data.
Solution: Use optional chaining when accessing nested booking fields since many are conditional. Check in OnceHub if video conferencing (Zoom, Google Meet, or Teams) is configured for the booking page — join_url only appears when a conference tool is connected. For missing assigned_member, confirm the booking page is set up with team scheduling (round-robin or assigned pools) rather than individual booking pages.
1// Safe access pattern for nested OnceHub fields:2const assignedTo = booking?.assigned_member?.name ?? 'Unassigned';3const meetingLink = booking?.join_url ?? '';Best practices
- Use the JavaScript embed widget for customer-facing booking flows — it handles time zone detection, mobile responsiveness, and confirmation emails automatically, saving weeks of development time.
- Store your OnceHub API key in .env.local for development and in your hosting platform's environment variables for production — never hardcode it in source files or expose it in client-side React code.
- Respond to webhooks with 200 immediately and process booking events asynchronously to prevent delivery timeouts. OnceHub will retry failed deliveries up to 3 times with exponential backoff.
- Always deploy to Netlify or Bolt Cloud before testing webhook integrations — Bolt's WebContainer cannot receive incoming HTTP requests from external services like OnceHub.
- Filter API requests by date range and limit results to 100 per page to keep response times fast. Fetching all historical bookings on a busy account can return thousands of records and slow down your dashboard.
- Validate webhook signatures using HMAC-SHA256 and your shared secret before processing events to prevent unauthorized requests from spoofing booking notifications.
- Test with OnceHub's sandbox or a test booking page before integrating live customer-facing booking pages — test bookings are real, so use an internal test page that does not send email confirmations to customers.
Alternatives
Calendly is simpler than OnceHub and better for individual scheduling without complex team routing — choose it when you need a fast embed without round-robin or load balancing requirements.
Acuity Scheduling (by Squarespace) is better suited for service businesses like salons and consultants that need intake forms, packages, and payment collection at booking time.
Zoom Scheduler provides basic scheduling linked directly to Zoom meetings, making it the simplest choice when video conferencing is the only use case and team routing is not needed.
Frequently asked questions
Does the OnceHub booking widget work in Bolt's preview without deploying?
Yes. The JavaScript embed widget loads from OnceHub's CDN and communicates directly with OnceHub's servers — it does not route through Bolt's WebContainer networking. This means you can embed the widget and have a fully functional booking experience in the Bolt preview. Only the API and webhook features require deployment.
Do I need a paid OnceHub plan to integrate with Bolt.new?
For the JavaScript embed widget, the free OnceHub trial is sufficient. For REST API access (fetching bookings, managing pages, receiving webhooks), a paid OnceHub plan is required. Check oncehub.com for current pricing — plans differ in the number of booking pages, team members, and API features included.
How do I test webhooks before deploying my Bolt app?
Use ngrok to create a temporary public tunnel to your local Next.js server. Run ngrok http 3000 in a terminal (install ngrok if needed), copy the HTTPS URL it generates, and register it as your webhook endpoint in OnceHub. This lets you receive and debug webhook events locally without a full deployment. Remember to redeploy and update the webhook URL once you are ready for production.
Can I use OnceHub with a Vite project in Bolt instead of Next.js?
The embed widget works in any React project, Vite or Next.js. For API calls in a Vite project, use a Vite proxy in vite.config.ts to route API requests through the dev server and avoid CORS issues. For production, you will need a serverless function platform (Netlify Functions, Vercel Functions) to handle the API key securely, since Vite builds client-only bundles without server-side execution.
How do I handle multiple OnceHub booking pages in one Bolt app?
Pass the booking page ID as a prop to the SchedulingWidget component — this makes it reusable across different pages and flows. For API calls, filter by booking_page_id in your query parameters to fetch bookings for specific pages. This is useful when your app has multiple service types (e.g., discovery calls, demos, onboarding sessions) each with their own booking page.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation