A self-hosted ticket reservation system built by Chobble CIC, a community interest company. Runs on Bunny Edge Scripting (or any Deno environment) with libsql. Encrypts all PII at rest. Handles free and paid events with Stripe or Square.
Website: tickets.chobble.com
This is not "open core" — every feature is available under AGPLv3 with no proprietary add-ons. Hosted instances available at tix.chobble.com for £50/year, no tiers.
The recommended deployment. Fork the repo, connect it to Bunny, and deploy via GitHub Actions. You can pull in upstream changes and push your own customisations on your own schedule.
-
Fork or clone this repository
-
Create a Bunny Database in the Bunny dashboard — note the database URL and token
-
Create a Bunny Edge Script using your repository as the linked source
-
Add secrets to the script in the Bunny dashboard:
Secret Description DB_URLYour Bunny database URL DB_TOKENYour Bunny database auth token DB_ENCRYPTION_KEY32-byte base64-encoded AES-256 key ALLOWED_DOMAINYour domain (e.g. tickets.example.com) -
Add GitHub Actions secrets to your repository:
BUNNY_SCRIPT_IDandBUNNY_ACCESS_KEY
Pushes to main trigger the deploy workflow automatically. The database schema auto-migrates on first request. Visit /setup/ to set your admin password and currency.
For image uploads, also add STORAGE_ZONE_NAME and STORAGE_ZONE_KEY as Bunny secrets. See the CONFIG_KEYS reference for all optional variables.
- Standard events (fixed capacity) and daily events (per-date capacity with calendar picker)
- Event groups for organising related events together
- Optional event date and location fields, displayed on the ticket page
- Configurable contact fields: email, phone, postal address (any combination)
- Special instructions field for attendee notes
- Terms and conditions — set globally in settings, attendees must agree before booking
- Capacity limits, max tickets per purchase, registration deadlines
- Multi-event booking — combine events in one URL (
/ticket/event1+event2), one form, one checkout - Multi-booking link builder on the dashboard for generating combined-event URLs
- Event QR code SVG (
/ticket/:slug/qr) for posters and printed materials - Embeddable via iframe with configurable CSP frame-ancestors
- Custom thank-you URL or default confirmation page
- Non-transferable tickets — per-event toggle requiring ID verification at check-in
- Event image and file attachment uploads (encrypted, stored on Bunny CDN)
- Manual attendee creation from the admin event page (walk-ins, comps)
- Stripe and Square with a pluggable provider interface
- Enter your API key in admin settings — webhook endpoint auto-configures
- Checkout sessions with metadata, webhook-driven attendee creation
- Configurable booking fee added to each transaction
- "Pay what you want" pricing with optional minimum and maximum
- Automatic refund if capacity exceeded after payment or event price changes during checkout
- Admin-issued full refunds for individual attendees or all attendees in bulk
- Each ticket gets a unique URL (
/t/:token) with a QR code - Staff scan QR to reach check-in page, toggle check-in/out
- Built-in QR scanner — open from an event page, uses device camera, check-in-only (no accidental check-outs)
- ID verification prompt for non-transferable events before completing check-in
- Cross-event detection: scanner warns if a ticket belongs to a different event
- Multi-ticket view for multi-event bookings (
/t/token1+token2)
- Generates
.pkpassfiles for Apple Wallet with event details and barcode - Standards-compliant web service API for automatic pass updates
- Configurable via admin settings or environment variables
- Event CRUD, duplicate, deactivate/reactivate, delete (requires typing event name)
- Calendar view for daily events with per-date attendee counts
- Attendee list with date filtering (daily events), check-in status filtering
- Attendee editing — update name, contact details, quantity, or reassign to a different event
- CSV export (respects active filters)
- Per-event and global activity log (creation, updates, check-ins, exports, refunds, deletions)
- Holiday/blackout date management for daily events
- Multi-user: owners invite managers via time-limited links (7-day expiry)
- Session management: view active sessions, kill all others
- Settings: payment provider config, email templates, custom domain, embed host restrictions, terms and conditions, password change
- Branding: custom header image, website title, theme colours
- Built-in admin guide (
/admin/guide) with FAQ for all features - Ntfy error notifications for production monitoring (optional)
- ICS calendar feed (
/feeds/events.ics) for calendar apps - RSS feed (
/feeds/events.rss) for feed readers
- Automatic confirmation email to attendees and notification email to admins on each registration
- Five HTTP API providers: Resend, Postmark, SendGrid, Mailgun (US/EU)
- Customisable email templates using Liquid syntax (subject, HTML body, text body)
- Built-in template filters:
currency(formats amounts) andpluralize - Configured in admin settings — optional, system works without it
- RESTful API for event listing and booking (
/api/events,/api/events/:slug,/api/events/:slug/availability,/api/events/:slug/book) - No API key required — same data as public booking pages
- CORS-enabled for cross-origin requests
- Outbound POST on every registration (free or paid) to per-event and/or global webhook URLs
- Payload: name, email, phone, address, amount, currency, payment ID, ticket URL, per-ticket details
- Multi-event bookings send one consolidated webhook
Example webhook payload
{
"event_type": "registration.completed",
"name": "Alice Smith",
"email": "[email protected]",
"phone": "+44 7700 900000",
"address": "42 Oak Lane, Bristol, BS1 1AA",
"special_instructions": "Wheelchair access needed",
"price_paid": 3000,
"currency": "GBP",
"payment_id": "pi_3abc123def456",
"ticket_url": "https://tickets.example.com/t/A1B2C3D4E5",
"tickets": [
{
"event_name": "Summer Workshop",
"event_slug": "summer-workshop",
"unit_price": 1500,
"quantity": 2,
"date": "2025-08-20",
"ticket_token": "A1B2C3D4E5"
}
],
"timestamp": "2025-08-20T14:30:00.000Z",
"business_email": "[email protected]"
}Prices are in the smallest currency unit (e.g. pence, cents). For multi-event bookings the tickets array contains one entry per event and the ticket_url combines tokens with +.
- Hybrid RSA-OAEP + AES-256-GCM for attendee PII (name, email, phone, postal address)
- Public key encrypts on submission (no auth needed)
- Private key only available to authenticated admin sessions
- A database dump alone is not sufficient to recover PII — an attacker would also need the encryption key from the environment
- AES-256-GCM for payment IDs, prices, check-in status, API keys, holiday names, usernames
- PBKDF2 (600k iterations, SHA-256) for password hashing
- Three-layer key hierarchy: env var root key → RSA key pair → per-user wrapped data keys
- Lost password = permanently unreadable data. No backdoor.
- Capacity check + insert in a single SQL statement to reduce the window for overbooking
- Payment webhook idempotency via two-phase locking on
processed_paymentstable - Stale reservation auto-cleanup after 5 minutes
- CSRF: double-submit cookie with 256-bit random tokens, path-scoped
- Rate limiting: 5 failed logins → 15-minute IP lockout (IPs HMAC-hashed before storage)
- Constant-time password comparison with random delay
- Session tokens hashed before database storage, 24-hour expiry, HttpOnly cookies
- Content-Type validation on all POST endpoints
These measures aim to raise the cost of common attacks. They do not guarantee security against all scenarios — proper operational practices (key management, access control, monitoring) are equally important.
docker build -t chobble-tickets .
docker run -p 3000:3000 \
-v tickets-data:/data \
-e DB_URL="file:/data/tickets.db" \
-e DB_ENCRYPTION_KEY="your-base64-key" \
-e ALLOWED_DOMAIN="your-domain.com" \
chobble-ticketsThe Dockerfile uses a local SQLite file by default. Set DB_URL and DB_TOKEN to point at a remote Turso database instead if preferred.
Deploy to: DigitalOcean | Heroku | Koyeb | Render
Also deployable with Fly.io (fly launch) or any Docker host.
# Install Deno, cache dependencies, run all checks
./setup.sh
# Run locally
DB_URL=libsql://your-db.turso.io DB_TOKEN=your-token \
DB_ENCRYPTION_KEY=your-base64-key ALLOWED_DOMAIN=localhost \
deno task start
# Run tests (stripe-mock downloaded automatically)
deno task testOn first launch, visit /setup/ to set admin credentials and currency. Payment providers are configured at /admin/settings.
deno task start # Run server
deno task test # Run tests
deno task test:coverage # Tests with coverage report
deno task lint # Lint
deno task fmt # Format
deno task typecheck # Type check
deno task build:edge # Build for Bunny Edge
deno task precommit # All checks (typecheck, lint, cpd, build:edge, test:coverage)
| Variable | Required | Description |
|---|---|---|
DB_URL |
Yes | libsql database URL |
DB_TOKEN |
Yes* | Database auth token (*remote databases) |
DB_ENCRYPTION_KEY |
Yes | 32-byte base64-encoded AES-256 key |
ALLOWED_DOMAIN |
Yes | Domain for security validation |
See the CONFIG_KEYS reference for all optional variables (email providers, Apple Wallet, image uploads, and more).
- Runtime: Deno — runs standalone, via Docker, or on Bunny Edge Scripting
- Database: libsql (local SQLite or remote Turso)
- Payments: Stripe, Square
- Build: esbuild, single-file output
- Templates: Server-rendered JSX
- Crypto: Web Crypto API (AES-256-GCM, RSA-OAEP, PBKDF2)
AGPLv3 — developed by Chobble CIC, a community interest company.