Skip to content

Embed the scheduler web component

Beginner ⏱ 10 minutes

Serve the Riposte scheduler widget, forward availability and booking requests, and make it feel native to your product.

Mount the widget in your app

Install the @riposte.co/scheduler package and load the web component anywhere you can render HTML. The portal generates the same snippet shown below so you can drop it directly into React, Next.js, or vanilla JavaScript:

npm install @riposte.co/scheduler
import '@riposte.co/scheduler'

export function SchedulerWidget() {
  return (
    <riposte-scheduler
      api-base-url="http://localhost:4000/riposte" // Example place where you're hosting the Riposte server
      experience-slug="demo-onboarding"
      duration-minutes="30"
      data-default-account-ids='["acct_host_1","acct_host_2"]'
      data-text-headline="Pick a time with our team"
    />
  )
}

Set api-base-url to the path or origin that proxies scheduler traffic (the element prefixes every availability and booking fetch with this value, see product/scheduler/src/index.ts:567). Then set experience-slug to the slug you created in the admin portal. Optional attributes like timezone, duration-minutes, and data-default-account-ids mirror the knobs inside the portal embed preview so you can keep frontend code minimal. Use data-text-* overrides for quick copy tweaks without redeploying your scheduler config.

Proxy availability and booking calls

The widget calls your backend when guests browse slots or submit the booking form. Forward those requests to Riposte's scheduler APIs so we can execute your configured callbacks. The admin portal's embed tab prints the same fetch helpers used below:

const availabilityResponse = await fetch('/api/scheduler/experiences/demo-onboarding/availability', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    range: {
      start: new Date().toISOString(),
      end: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
    },
    durationMinutes: 30,
    // timezone: 'UTC',
    // attendees: [{ role: 'guest', email: '[email protected]' }],
  }),
})
const availability = await availabilityResponse.json()

const bookingResponse = await fetch('/api/scheduler/experiences/demo-onboarding/bookings', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    slot: availability.slots[0],
    guest: {
      email: '[email protected]',
      name: 'Guest User',
    },
  }),
})
const booking = await bookingResponse.json()

To implement round-robin routing, intercept the availability payload before forwarding it upstream. Tag the request with the next host (stored anywhere you like) and let your callback honor the metadata:

const hosts = ['acct_host_1', 'acct_host_2']
let nextHost = 0

app.post('/api/scheduler/experiences/:slug/availability', async (request, reply) => {
  const hostAccountId = hosts[nextHost]
  nextHost = (nextHost + 1) % hosts.length

  const response = await sdk.scheduler.resolveAvailabilityViaCallback({
    pathParams: { slug: request.params.slug },
    body: {
      ...request.body,
      metadata: { hostAccountId },
    },
    contentType: 'application/json',
  })

  const status = Number(response.status) || 200
  reply.status(status).send(response.data ?? null)
})

Your availability callback receives metadata.hostAccountId so it can lock slots to a specific teammate. Use the same pattern in the bookings route to persist who claimed the meeting.

Style and extend the UI

Scheduler experiences carry a ui block for long-lived defaults, but you can still tweak the surface without redeploying. Combine data attributes with lightweight CSS for brand alignment:

.scheduler-container {
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.12);
  border-radius: 18px;
}

Populate ui.theme (colors, typography, and radius), ui.copy, or ui.layout in the admin portal to generate a JSON snapshot that you can commit alongside your codebase. When you need runtime experiments, push temporary overrides through data-text-* attributes or CSS classes the same way the portal preview does.

Additional resources