Skip to content

Build around
your studio.

Connect your own tools with the REST API, TypeScript SDK, and webhooks. Build on the same API that powers the studio console.

Webhook deliveriesnewest first
EventEndpointStateTriesAnswer
booking.createdhooks.example.com/soundslotdelivered1200
payment.succeededhooks.example.com/soundslotdelivered1200
series.occurrence_skippeddesk.example.com/bookingsdelivered2200
booking.cancelleddesk.example.com/bookingspending3502
payout.faileddesk.example.com/bookingsfailed8no answer

Start building.

One REST API for rooms, sessions, customers and money, a TypeScript SDK generated from it, and an event for anything that changes. The console and the storefront are clients of the same thing.

TypeScript clientnpm i @soundslot/sdk-public
Theme CLInpx @soundslot/cli theme init my-theme
n8nn8n-nodes-soundslot

Not writing code? The Soundslot app for Zapier and the node for n8n carry every event as a trigger and customers and bookings as actions.

API access, webhooks and the SDK are on every plan

Follow every delivery.

The studio's own delivery log, in the console and on the API. What went out, how many attempts it took, and what the receiving server said.

Everything that happens, as it happens.

Register a URL, pick the events you want, and we deliver them. This is the whole list, read out of the same file the platform emits from — not a sample of it.

webhook events41 types

booking

booking.created
a booking was made (storefront or desk); a storefront booking is unpaid until payment.succeeded
booking.updated
time, room, or details changed
booking.cancelled
cancelled by the customer or the desk
booking.approved
an approval-room booking was confirmed; card captured
booking.declined
declined; the card hold was released
booking.completed
a session's time passed with nobody saying otherwise
booking.no_show
staff marked a session a no-show
booking.abandoned
a checkout was started and never paid; the hold lapsed
booking.extended
time was added to a running session
booking.change_requested
a customer asked to move an approval-room session

class

class.published
a class went on sale
class.updated
a class was edited — the room-time, the price or how many seats

payment

payment.succeeded
money in — booking paid
payment.authorized
card hold placed for an approval room
payment.failed
a charge or auto-charge failed
payment.refunded
refund issued (card or account credit)
payment.recorded
the desk recorded money taken outside the platform
payment.disputed
a cardholder disputed a charge

payout

payout.failed
a payout to your bank was rejected — the money is still in Stripe

series

series.created
a recurring weekly slot was set up
series.updated
series edited — applies forward only
series.ended
a recurring slot was ended
series.change_requested
a customer asked to change their recurring slot
series.occurrence_skipped
one week of a series was skipped
series.occurrence_removed
one week of a series was removed
series.occurrence_restored
a skipped week was restored

hour_pack

hour_pack.purchased
a customer bought an hour pack; account credit granted

gift_card

gift_card.purchased
somebody bought a gift card; the code is now live
gift_card.redeemed
a gift card was handed in; credit granted

review

review.created
a customer rated a session; it may be published or waiting

rental

rental.reserved
gear was reserved
rental.overdue
rented gear was not returned on time

tenancy

tenancy.created
a monthly lockout was set up
tenancy.charged
a monthly lockout's rent was collected
tenancy.charge_failed
a monthly lockout's rent charge failed
tenancy.ended
a monthly lockout ended

membership

membership.started
a customer joined a membership plan
membership.charged
a membership's monthly fee was collected
membership.charge_failed
a membership's monthly charge failed
membership.ended
a membership was cancelled or ran out of retries

flow

flow.staff_notification
a flow asked to notify staff

One delivery, and this is the whole body.

Every event arrives as the same four keys: the delivery id, the type, when it fired, and the data for that type. A booking carries the booking, priced and settled, so a receiver needs no second call to make sense of it.

webhook-id
The delivery id. Stable across retries, so it is your dedupe key.
webhook-timestamp
Unix seconds, signed, so a captured request cannot be replayed at you later.
webhook-signature
HMAC over id.timestamp.body with the whsec_ secret shown once at registration.

Verify with any Standard Webhooks library, and verify before you parse.

POST https://hooks.example.com/soundslotbooking.created
{
  "id": "7f0a1c6e-3d21-4a8f-9c0b-2ab55e9d1042",
  "type": "booking.created",
  "timestamp": "2026-09-07T18:04:11.216Z",
  "data": {
    "booking": {
      "id": "b1f4c0d2-8e77-4b19-9d3a-51c6f2a7e884",
      "roomId": "3c9a7e21-45bd-4f10-8a62-9d7c1b40e553",
      "locationId": "0c2f8d31-77ae-4c05-93b1-6e4a2d8f1c90",
      "customerId": "5a83be04-1c6d-4e77-b219-8f0c3a6d4b21",
      "activityId": null,
      "staffId": null,
      "kind": "session",
      "customFields": {},
      "seriesId": null,
      "bandId": null,
      "startsAt": "2026-09-08T18:00:00.000Z",
      "endsAt": "2026-09-08T20:00:00.000Z",
      "status": "confirmed",
      "subtotalCents": 4000,
      "taxCents": 0,
      "totalCents": 4000,
      "creditAppliedCents": 0,
      "paymentStatus": "paid",
      "paymentPlan": "full",
      "depositCents": null,
      "tipCents": 500,
      "bandName": null,
      "discountCents": 0,
      "promotionCode": null,
      "balance": null,
      "offlinePaymentMethod": null,
      "offlinePaymentNote": null,
      "paidAt": "2026-09-07T18:04:10.884Z",
      "notes": null,
      "source": "storefront",
      "cancelledAt": null,
      "cancelReason": null,
      "checkedInAt": null,
      "createdAt": "2026-09-07T18:04:10.612Z"
    }
  }
}

Your server was down. Ours kept trying.

Each event is sent up to 8 times, the wait doubling after every refusal. Every attempt is on the record, and nothing needs polling.

retry schedule8 attempts
AttemptWaitAfter the event
1sent—
230s30s
31m1m 30s
42m3m 30s
54m7m 30s
68m15m 30s
716m31m 30s
832m63m 30s
Answer 2xx and get on with it
Store the delivery, answer, then do the work. Anything else — or no answer at all — is retried.
A failure keeps the evidence
Your status code and the first 2,000 characters of your response are stored against the delivery. A request that got no answer records why instead: DNS, refused, an expired certificate.
The last attempt closes it
The delivery is marked failed and stays visible. GET /api/v1/admin/webhook-deliveries lists them newest first, filterable by state and by endpoint.
A replay is a new delivery
It carries the same payload and its own webhook-id, so a receiver deduping on that header will process it again. That is the point of a replay.

The console is a client of the API.

There is no smaller read-only mirror to drift from the real thing. Availability is worked out live — opening hours, minus blackouts, minus what is booked, with minimums and turnover buffers already applied — so what the API hands back is what can be booked.

book-a-room.ts
import { client } from "@soundslot/sdk-public/client";
import { adminCreateBooking, storefrontGetAvailability } from "@soundslot/sdk-public";

client.setConfig({
  headers: { Authorization: `Bearer ${process.env.SOUNDSLOT_API_KEY}` },
});

// what can actually be booked in this room on this day, in the location's zone
const day = await storefrontGetAvailability({
  path: { slug: "riff-city" },
  query: { roomId, date: "2026-09-14" },
});

await adminCreateBooking({
  body: { roomId, customerId, startsAt, endsAt, payment: "send_link" },
});

Sections of the reference

  • Recording projects
  • Bookings
  • Series
  • Classes
  • Rooms
  • Catalog
  • Customers
  • Staff
  • Roles
  • Rentals
  • Revenue
  • Reports
  • Messaging
  • Flows
  • Billing
  • Payments
  • Studio
  • Themes
  • Media
  • Documents
  • Tenancies
  • Developers
  • Storefront
  • Checkout
  • Calendar
  • Webhooks
two documents, one registryopenapi 3.1
/api/openapi.json

The public document, and what the TypeScript SDK is generated from. Rendered at /api/docs, with every webhook event and its exact body at the bottom.

/api/internal/openapi.json

The same registry with the platform control plane still in it. Gated to platform admins, never published. The public one is derived from it by dropping those operations and pruning any schema they alone referenced.

ways in3 schemes
bearerAuthAuthorization: Bearer ssk_…
An API key, minted in the console under Developers and shown once. It acts as the person who issued it, so every permission and tenant check runs unchanged.
staffSession__Host-staff-auth.session_token
The console's own cookie. Browser flows only — an integration uses a key.
customerSession__Host-customer-auth.session_token
A storefront customer. Browsing needs none; their account, saved cards and receipts do.

Build the storefront itself.

A theme is a folder of Liquid, JSON and CSS in the shape Shopify developers already know: a layout, sections with schemas, JSON templates and a settings schema. Start from the reference theme with npx @soundslot/cli theme init my-theme, then push it and a studio switches to it.

Read the theme guide

Most of this is a Zap, with no code to write.

Point Webhooks by Zapier, Make or n8n at your studio and the payload above is the trigger. These six are afternoons, not projects.

A booking is confirmed
Drop it on the shared studio calendar the whole team already watches
Google Calendar · Outlook
Someone cancels
Ping the desk, clear the calendar, and text whoever wanted that slot
Slack · Twilio · Google Calendar
A payment lands
Append a row your bookkeeper can reconcile at month end
Google Sheets · Airtable
A new customer signs up
Add them to the mailing list, tagged by the room they booked
Mailchimp · Klaviyo
A card fails on a weekly slot
Tell you before the band turns up, not after
Slack · email · SMS
A payout to your bank is rejected
Wake somebody up — the money is still sitting in Stripe until you fix it
Slack · SMS · PagerDuty