All docs

Quickstart: free times and a booking

From a key to a confirmed, paid booking in six steps with the TypeScript SDK. A publishable key does everything up to the last step.

Updated 8 October 2026 About 3 minutes View as Markdown

Before you start, you need a project with bookings switched on, at least one service that somebody on the staff does, and a publishable key from the project's API keys screen. For a service that asks for payment, payments has to be switched on and connected too, and the last step needs a webhook endpoint pointing at your server.

1. Install the two packages

terminal
npm install @backendfree/core @backendfree/bookings

Both are plain ES modules with no dependencies.

2. Create a client

booking.ts
import { Client } from '@backendfree/core';
import { Bookings } from '@backendfree/bookings';

const bookings = new Bookings(new Client({
  origin: 'https://backendfree.com',
  key: 'pk_live_...',
}));

The origin is required. The key says which website is asking, so no path ever names one.

Constructing a client with a secret key in a browser throws. A secret key in a page is every customer and every payment handed to whoever opens the network tab.

3. List services and free times

booking.ts
const { results: services } = await bookings.services();
const { slots, timezone } = await bookings.availability({
  service: services[0].id,
});

from defaults to today on the business's own clock. Each slot is an instant with the business's timezone beside it, so a time means the same thing in every visitor's browser: see Times and timezones.

4. Book a slot

booking.ts
const booking = await bookings.book({
  service: services[0].id,
  starts_at: slots[0].start,
  customer_email: 'sam@example.com',
  success_url: 'https://salon.example/thanks?booking={id}',
  cancel_url: 'https://salon.example/book',
});

{id} in a return address becomes the new booking's id, so the page the customer comes back to knows which booking to read.

A publishable key is enough to take a booking. What bounds it is what one visitor can do: each address may make 10/min and 30/hour writes, an IPv6 network counting as one address.

book() returns manage_token exactly once. Whoever holds it can read, cancel or move that one booking, so keep it the way you would keep a password.

5. Send the customer to pay, if the service asks

booking.ts
if (booking.payment?.required) {
  window.location.href = booking.payment.checkout_url;
}

A booking that has to be paid comes back pending_payment with a checkout address. The slot is held only until payment.expires_at, 15 minutes unless the business changes it.

6. Confirm it on your server

The page the customer lands on afterwards proves they came back, not that they paid. Nothing confirms a booking except the payment provider's own message, which reaches your server as a signed webhook.

webhook.ts
import { verifyWebhook } from '@backendfree/core';

const event = await verifyWebhook({
  body: await request.text(),
  headers: request.headers,
  secret: process.env.BACKENDFREE_WEBHOOK_SECRET!,
});

if (event.name === 'booking.confirmed') {
  // confirmed, and paid for if it had to be
}

Answer with a 2xx quickly and do the work afterwards, and make the handler idempotent on event.delivery, since a delivery arrives at least once. Verify a delivery explains both.