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
npm install @backendfree/core @backendfree/bookings
Both are plain ES modules with no dependencies.
2. Create a client
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
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
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()returnsmanage_tokenexactly 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
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.
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.