The platform Curling Climbing Verticals Pricing Book a walkthrough

The platform

What every club gets, before the sport.

Members, waivers, check-in, doors, bookings, money, messaging — fifteen things every club needs, built once and maintained for all of them. Your sport's module is the small part. This page is the large part.

Two club volunteers at a folding table working through the draw on paper and a laptop, trophy shelf behind them

Fifteen modules. Every venue.

Maintained once, for everybody. A fix to the waiver engine is a fix at every venue on the platform, whatever sport they happen to play.

Members
Roster, roles, portal logins, emergency contacts, guardians for minors.
Registration
A public multi-step join form, an office approval queue, and payment online or at the desk.
Terms & rates
A price list per venue — prepaid, recurring, punch card, drop-in — with one current term enforced by the schema.
Waivers
Editable documents with clauses that appear on a birthdate given two fields earlier. Signed in the app, on the website, or from a public link with no login. Guardian rules, signed PDFs, verification codes.
Check-in
A rotating QR in the app scanned at the desk, or a printed code by the door scanned by the member. Raises a lapsed certificate or an unsigned waiver — and refuses nobody.
Door access
Fobs and cards, with groups derived from membership, role and clearance, pushed to Kisi or Brivo. The door log is pulled back.
Bookings
Enquiries may overlap, because two people may ask about the same Saturday. Confirmed bookings may not — a Postgres exclusion constraint sees to it.
Money
Invoices, payments, a whole term or season billed in one pass, and the treasurer's summary.
Payments
Stripe Connect direct charges with a platform commission, plus cash, cheque, e-transfer and card taken at the desk.
Communications
Email and push broadcasts segmented by programme, team, role or entrant. A member-to-desk message thread.
Duties & shifts
The rota, whatever it is called locally — bar shifts, desk shifts, ice crew, belay rota, draw masters.
Content
News, calendar, photo galleries, documents, sponsors, awards.
Public feeds
JSON for the website, plus an iCal feed members can subscribe to.
Multi-tenancy
Many venues in one database, isolated by row-level security. Platform logins are a separate table from venue logins, on purpose.
Platform console
Onboard a venue, set its commission, see revenue by month and by venue, read the access log.

Four pieces of software, one deployment.

The website is static and can go live on its own. The console and the app both talk to the same API, which serves every venue on the platform at once.

01 — static HTML

The website

A drop-in replacement for the venue's current site. Plain HTML, CSS and vanilla JavaScript — no Node, no build server, nothing to keep running. One self-contained directory per venue, uploaded to their own domain.

  • Register, sign a waiver, check in — maintained once, the same everywhere
  • Colours, type and content are the venue's own
  • The build refuses to finish if a page links outside its own directory
02 — React SPA

The office console

Where the venue actually runs. The desk, the office, the committee. Rosters, approvals, the schedule, the till, the rota, the broadcast — and the vertical's own tooling sitting alongside it.

  • Roles from front desk to treasurer to administrator
  • Every cross-venue read is written to an audit log
  • Onboards a venue and its first administrator in one transaction
03 — Expo / React Native

The companion app

The members' side. Their next booking, their card, their waiver, their bill. A rotating QR code for the door that works with no signal at all.

  • Sign a waiver on the phone, or from a public link with no account
  • Push broadcasts segmented the same way email is
  • The check-in code is derived on the device, not fetched
04 — Express + Postgres

The API

One deployment serving every venue on the platform. Tenancy is enforced by Postgres row-level security, not by convention — a missing WHERE clause returns nothing rather than somebody else's members.

  • One connection, one transaction, one tenant per request
  • Constraints in the database, not just the handler
  • JSON feeds and iCal for the public site

A missing WHERE returns nothing.

Many venues live in one database. Isolation is enforced by Postgres row-level security, not by remembering to filter — because the one thing a venue will never forgive is seeing somebody else's members, and "we were careful" is not an architecture.

Each request takes one connection and runs inside one transaction, which is what carries the tenant identity. Get the query wrong and you get an empty result. You do not get another club's roster.

Platform administrators sign in against a different table from venue users, separated by token issuer. Without that, a venue administrator — who can create venue users — could grant themselves the ability to read every other venue. There is a test for exactly that, in both directions.

Enforced, not intended

  • Row-level security on every tenant table
  • One connection, one transaction, one tenant, per request
  • Platform logins in a separate table from venue logins
  • Token kinds separated by issuer, not by a claim
  • Every cross-venue read written to an audit log
  • Uniqueness is per venue — two clubs may both have a J. Smith
  • Suspending a customer hides nothing and deletes nothing

Constraints, not good intentions.

The rules that would be most embarrassing to get wrong are the ones the handler is not trusted with. They live in the schema, and every one of them is exercised over HTTP by a verification script — because a constraint nobody has seen fire is a constraint that might not be there.

exclusion constraint

No double booking

Two confirmed bookings can never overlap in the same room, sheet, court or lane. Enquiries may overlap, because two people may ask about the same Saturday — and the desk needs to see both before choosing.

partial unique index

One loan per item

A harness cannot be out with two people at once, however the desk is clicked. The same shape covers anything the venue lends: a rental locker, a set of rocks, a boat.

unique constraint

One live certificate

Exactly one current qualification per member per code, so a re-assessment replaces rather than stacks, and a revocation takes away the access it granted.

computed view

Standings follow the scores

The table is computed from games rather than stored beside them, so correcting a scoresheet in March corrects the standings in the same breath. Nobody reruns anything.

The venue is paid directly.

Stripe Connect direct charges: the money lands in the venue's own Stripe account, and the platform takes a commission on the way past. Onboarding is Stripe's, not ours — we never hold the venue's funds.

01

Online

Stripe Checkout from the registration form, the bonspiel entry, the competition entry or the members' app. Commission is set per venue in the platform console.

02

At the desk

Cash, cheque, e-transfer and card, recorded against the same invoice. A club that runs half its money through a shoebox is not a club that will switch.

03

In one pass

A whole season or term billed at once, with memberships and programme places on one bill per member, and a treasurer's summary that reconciles.

Learned the hard way.

Every platform has a couple of these. Ours are written down because they will bite anybody who reimplements this from scratch.

timezones

A naive timestamp is read in the server's zone

A course meeting Tuesday at seven was stored at three in the afternoon, and moved another hour when the clocks went back. Wall-clock times are now converted in the venue's timezone — not the server's, because one deployment serves venues in different provinces.

transactions

"Fire and forget" cannot be made safe with a try/catch

A request holds one connection inside one transaction, which is what makes row-level security work. A failed write marks the whole transaction aborted, and the handler's next statement dies pointing at the wrong line. Best-effort work gets a savepoint, or is deferred to just before the commit.

Is venueOS multi-tenant?

Yes. Many venues share one deployment and one database, isolated by Postgres row-level security rather than by convention — a missing WHERE clause returns nothing, not another venue's members. Platform logins are a separate table from venue logins.

What is the tech stack?

Static HTML websites per venue, a React office console, an Expo / React Native members' app, and an Express API on Postgres with Stripe Connect, Kisi and Brivo integrations, plus JSON and iCal public feeds.

Can we add a vertical that is not listed?

If it has a roster, a liability, a finite resource and a season, most of it is already built. The verticals page ranks candidates honestly by how much of the core they reuse; tell us what your venue does and we will say which column it lands in.

Next step

See it against your season.

The quickest way through this is a walkthrough driven by real data — a season drawn, scheduled, scored, billed and broadcast, in about thirty minutes.

Book a walkthrough