DEVELOPERS
Your bookings, in your systems.
The FormSide API is a read-only REST API for a sports camp organiser’s camps and bookings, with signed webhooks that fire the moment a booking is created, confirmed, cancelled, paid or reviewed. It is built for club websites, finance exports, Zapier and Make, and the spreadsheet someone still swears by.
AUTHENTICATION
One header. That is the whole setup.
An owner or admin creates a key under Dashboard → Developers. Keys look like fsk_live_…, are shown once, and can be revoked at any time. Send the key as a bearer token over HTTPS. Every key reads one organisation only.
curl https://www.formside.app/api/v1/camps \
-H "Authorization: Bearer fsk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"Missing or revoked keys get 401; a key without the needed scope gets 403. Errors are always JSON: { "error": { "code": "unauthorized", "message": "…" } }.
ENDPOINTS
Four endpoints, all read-only.
Base URL https://www.formside.app/api/v1. Lists return newest first and page with a cursor: pass ?limit= (1–100, default 50) and the next_cursor from the previous page as ?cursor=. A null next_cursor means you have everything.
GET/api/v1/camps
Published camps for your organisation with live places left. Drafts and archived camps are never returned.
{
"data": [
{
"id": "44444444-4444-4444-8444-444444444444",
"slug": "girls-game-changers-day",
"title": "Girls' Game Changers Day",
"sport": "Ice hockey",
"status": "published",
"starts_at": "2026-11-08T09:30:00+00:00",
"ends_at": "2026-11-08T16:00:00+00:00",
"venue": "Guildford Spectrum",
"city": "Surrey",
"price_minor": 7900,
"currency": "GBP",
"capacity": 42,
"places_left": 42,
"booking_url": "https://www.formside.app/book/girls-game-changers-day",
"created_at": "2026-08-22T08:30:36+00:00",
"updated_at": "2026-08-22T08:30:36+00:00"
}
],
"next_cursor": null
}GET/api/v1/camps/{id}
One camp, same shape, wrapped in data. 404 if it is not yours or not published.
GET/api/v1/bookings
Bookings across all camps. Filter with ?camp_id=, ?status= (reserved, confirmed, cancelled, expired, attended, no_show) and ?updated_since= (ISO 8601) — the last one is how you poll for changes cheaply.
The participant is first name and last initial only. Medical notes, consents, emergency contacts and form answers are never exposed over the API.
curl "https://www.formside.app/api/v1/bookings?status=confirmed&updated_since=2026-08-01T00:00:00Z&limit=2" \
-H "Authorization: Bearer fsk_live_…"
{
"data": [
{
"id": "26ea877c-3348-43b5-bdb7-3e8ef83a3436",
"booking_number": "FSD-26-8MJ3KSEJXRP",
"status": "confirmed",
"payment_status": "paid",
"camp_id": "e15f95aa-68c8-4b14-8a73-ceca6cbc14b4",
"participant": { "first_name": "Zara", "last_initial": "K" },
"booker_email": "aisha.khan@example.com",
"amounts": {
"amount_minor": 18500,
"total_minor": 18500,
"balance_due_minor": 0,
"discount_minor": 0,
"addons_minor": 0,
"currency": "GBP"
},
"paid_at": "2026-08-06T08:31:52+00:00",
"created_at": "2026-08-01T08:31:52+00:00",
"updated_at": "2026-08-22T08:31:52+00:00"
}
],
"next_cursor": "eyJjIjoiMjAyNi0wOC0wMVQwODozMTo1Mi44MjgwNyswMDowMCIsImkiOiIyNmVhODc3Yy0uLi4ifQ"
}GET/api/v1/bookings/{id}
One booking, same shape, wrapped in data.
WEBHOOKS
Hear about it the moment it happens.
Add an https endpoint under Dashboard → Developers and choose the events you want. Each event is POSTed as JSON with the booking in the same shape as the API.
| Event | Fires when |
|---|---|
booking.created | A family completes the form and a place is reserved. |
booking.confirmed | The booking is confirmed (normally when payment lands). |
booking.cancelled | A booking is cancelled by you or the family. Includes the reason. |
payment.received | A card or bank payment is matched. Includes the payment amount and provider. |
review.submitted | A family submits a post-camp review. Includes rating and consent to publish. |
ping | You press Send test in the dashboard. |
The request
POST https://example.com/hooks/formside
Content-Type: application/json
X-FormSide-Event: booking.confirmed
X-FormSide-Delivery: 9d4a0c3e-… # unique per delivery; use it to de-duplicate
X-FormSide-Signature: t=1787500000,v1=5f1b… # see below
{
"id": "9d4a0c3e-…",
"event": "booking.confirmed",
"created_at": "2026-08-22T10:15:00+00:00",
"data": {
"booking": { "id": "…", "booking_number": "FSD-26-…", "status": "confirmed", "...": "…" }
}
}Verifying the signature
Every endpoint has a secret, shown once when you add it. The signature is an HMAC-SHA256 of the timestamp, a full stop, and the raw request body. Compare in constant time, and reject timestamps older than five minutes to stop replays.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return expected.length === parts.v1.length
&& timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Responding and retries
Reply with any 2xx within ten seconds and the delivery is done. A 4xx (other than 408 and 429) is treated as permanent and not retried. Anything else — a 5xx, a timeout, a refused connection — is retried up to eight times with gaps of 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and then 24 hours. Deliveries can arrive out of order and, rarely, more than once; the X-FormSide-Delivery id is stable across retries so you can de-duplicate on it. The dashboard shows recent deliveries with their response codes and lets you retry any of them by hand.
RATE LIMITS
120 requests a minute, per key.
Over that you receive 429 with a Retry-After header. For keeping a copy of bookings in sync, poll /api/v1/bookings?updated_since= every few minutes or, better, subscribe to webhooks and fetch only what changed. Up to 20 active keys and 10 webhook endpoints per organisation.
READY WHEN YOU ARE
Create a key and make your first call in under a minute.
Owners and admins find it under Dashboard → Developers. No approval queue, no sandbox keys to swap later. The non-technical overview is on the product page, and serving camp pages from your own domain is covered in the help centre.
ABOUT FORMSIDE
FormSide is a booking system for UK sports camps and weekly training programmes, built in the United Kingdom. It takes bookings and payments by card, bank transfer, instalments or cash, then runs the registration desk: QR check-in, medical notes, coach groups, an incident log and a register that records who collected each child. FormSide is free while it is in beta, with no limit on live camps or team members and no commission on bookings; organisers will be given notice before pricing changes. Support for organisers and families is by email at support@formside.app, privacy requests go to privacy@formside.app, and the help centre covers how it works step by step.