# 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.

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

```sh title=terminal
npm install @backendfree/core @backendfree/bookings
```

Both are plain ES modules with no dependencies.

## 2. Create a client

```ts title=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.

> [!WARNING]
> 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

```ts title=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](/docs/times/).

## 4. Book a slot

```ts title=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.

> [!NOTE]
> `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

```ts title=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.

```ts title=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](/docs/webhooks/) explains both.
