Booking Integration

Embed the NotaryCentral Booking Calendar

Host the appointment booking journey on your own site while NotaryCentral powers schedules, services, and secure Stripe payment.

β€’

NotaryCentral supports a fully embedded booking experience, allowing you to host the client appointment scheduler and payment checkout on your own domain while secure scheduling operations run inside an iframe.

Why embed the booking portal?

Embedding the booking portal on your website allows you to provide a unified, premium scheduling experience for your clients. Instead of redirecting them away to a generic booking link, they remain on your trusted domain throughout:

  • Selecting notary services or packages.
  • Checking real-time date and time availability.
  • Providing participant details and uploading documents.
  • Completing secure Stripe card payments if required.
  • Viewing booking confirmations and receipt summaries.

Step 1: Configure Settings

Before embedding the booking portal on your website, you must approve your parent website's domain in your NotaryCentral settings. This is a critical security step that configures the Content Security Policy (CSP) and allows your site to frame the calendar without browser blocks.

Getting started in the app

In app.notarycentral.org:

  1. Open the NotaryCentral app on your desktop or mobile device.
  2. Tap or click on the Menu.
  3. Search for Embedded customer experience settings.
  4. If you are navigating directly, the page path is /search/settings/embed-customer-experience.
  5. Scroll down to the Booking/calendar experience section.

What each field means

Enable embed: Toggle this switch to ON to allow NotaryCentral calendar flows to be loaded inside an iframe.

Booking embedder page URL: Enter the exact page URL where your embedded calendar iframe will live. Use the full HTTPS page URL in production, for example:

https://yourdomain.com/book

For local development, this may be:

http://localhost:5175/

Do not add query strings or trailing fragments in this settings field.

Booking allowed origin: Enter only the origin of your parent page if you want to explicitly allow a parent website origin. The origin is the scheme, host, and optional port, with no path, query string, or fragment. For example:

https://yourdomain.com

For local development, this may be:

http://localhost:5175

This should match the origin of the page that hosts the iframe.

Important: All URLs must include the http:// or https:// protocol. Do not add credentials or trailing fragments in these settings fields.

Step 2: Set Up the Developer Sample Code

To make the integration as seamless as possible, we provide a complete reference implementation on GitHub: NotaryCentral Embedded Booking React Sample at https://github.com/NotaryCentral/embedded-booking-react-sample.

This sample demonstrates two golden principles of premium embedding:

  1. Dumb Parent Parameter Forwarding: The parent container is completely dumb. It simply takes all query parameters from its own URL (including subdomain overrides and Stripe's post-payment redirect nc_path) and forwards them straight to the iframe. The iframe handles its own internal sub-routing.
  2. Dynamic Height Telemetry: The parent listens to notarycentral.calendar.resize events broadcasted from the iframe to dynamically resize its height, completely eliminating nested scrollbars and providing a native-app feel.

Reference React Implementation (App.tsx):

import { useState, useEffect } from 'react'
import './App.css'

function App() {
  const [loaded, setLoaded] = useState(false)
  const [iframeUrl, setIframeUrl] = useState('')
  const [iframeHeight, setIframeHeight] = useState('800px')

  useEffect(() => {
    if (typeof window !== 'undefined') {
      const parentOrigin = window.location.origin
      const baseUrl = 'http://localhost:3001' // Your local or production calendar URL
      
      // 1. DUMB PARENT FORWARDING: Grab all query parameters from parent URL
      const searchParams = new URLSearchParams(window.location.search)
      
      // Provide default fallbacks for local testing if parameters aren't supplied
      if (!searchParams.has('subdomain') && !searchParams.has('user')) {
        searchParams.set('subdomain', 'user56331')
      }
      searchParams.set('embed', 'true')
      searchParams.set('parentOrigin', parentOrigin)
      
      // Set the final iframe source URL with all search parameters preserved
      setIframeUrl(`${baseUrl}/?${searchParams.toString()}`)
    }

    // 2. DYNAMIC RESIZE TELEMETRY: Listen for height changes from the iframe
    const handleMessage = (event: MessageEvent) => {
      const data = event.data
      if (data && data.source === 'notarycentral') {
        if (data.event === 'notarycentral.calendar.resize' && data.payload?.height) {
          setIframeHeight(`${data.payload.height}px`)
        }
      }
    }

    window.addEventListener('message', handleMessage)
    return () => window.removeEventListener('message', handleMessage)
  }, [])

  if (!iframeUrl) {
    return null
  }

  return (
    <div className="app-container">
      {/* Optional: Premium Animated Loader Screen */}
      <div className={`loader-overlay ${loaded ? 'fade-out' : ''}`}>
        <div className="spinner" />
        <p>Connecting to Booking Portal...</p>
      </div>

      {/* Embedded Booking Iframe */}
      <iframe
        key={iframeUrl}
        src={iframeUrl}
        style={{ height: iframeHeight }}
        className={`booking-iframe ${loaded ? 'loaded' : ''}`}
        onLoad={() => setLoaded(true)}
        allow="payment; camera; microphone; display-capture; geolocation; clipboard-read; clipboard-write; fullscreen"
        title="NotaryCentral Embedded Booking"
      />
    </div>
  )
}

export default App

Reference CSS Layout (App.css):

#root {
  width: 100% !important;
  max-width: 100% !important;
  margin: 0 !important;
  border-inline: none !important;
}

body, html {
  margin: 0;
  padding: 0;
  width: 100%;
  min-height: 100%;
  background-color: transparent;
}

.app-container {
  position: relative;
  width: 100%;
  min-height: 100vh;
  background: transparent;
}

.booking-iframe {
  width: 100%;
  border: none;
  display: block;
  opacity: 0;
  transition: opacity 0.8s ease-in-out, height 0.2s ease-out;
}

.booking-iframe.loaded {
  opacity: 1;
}

Required Iframe Permissions

Because the booking calendar can host interactive features, you must declare appropriate permissions in the allow attribute of your iframe:

allow="payment; camera; microphone; display-capture; geolocation; clipboard-read; clipboard-write; fullscreen"

payment: Required to safely authorize Stripe Checkout sessions inside the framed environment.

camera and microphone: Highly recommended so your clients can test their setup or complete system prerequisites prior to their actual appointments.

Best Practices & Troubleshooting

Why is my calendar showing a Booking Calendar Unavailable error?

Verify that you have provided the correct subdomain query parameter. If you are developing locally, your parent page must forward the subdomain (or user) parameter down to the iframe. Our sample automatically falls back to user56331 for local testing.

How do I prevent nested scrollbars?

Never set a hard height on your iframe or set overflow: hidden on your parent containers. By listening to the notarycentral.calendar.resize postMessage events, your parent page will dynamically scale the iframe's height to match its exact content, allowing your users to scroll naturally using only the parent window's scrollbar.

What happens after payment completion?

Upon completing a Stripe checkout, Stripe redirects the client back to your parent website's URL. The redirect URL will contain an nc_path query parameter containing the path to the booking confirmation screen. Because of our dumb parent forwarding design, the parent app will automatically load that new path directly inside the iframe, loading the confirmation screen flawlessly!