How It Works – Qwikr Events
A quieter kind of platform

One deployment.
Every client.

A single Bunny Edge Script serves unlimited companies from one codebase. Here’s exactly how tenant isolation, provisioning, request routing, and data security work — no hand-waving.

🏘️

Deploy once

One Edge Script runs on Bunny’s global network. No per-tenant infrastructure, no separate deployments, no region configuration. Add a client row — they’re live.

🔒

Isolated by design

Every company gets its own Bunny Database. Not a shared schema with row filters — a physically separate database. True isolation from day one.

Edge-native performance

Requests are handled at Bunny’s nearest PoP, globally. Deno TypeScript runtime with no cold starts. Sub-50ms response times worldwide.

1 Deploy

One script runs everything

The entire platform is a single TypeScript file deployed to Bunny Edge Scripting. It reads the Host header on every incoming request to determine which company is being served — then routes to the right handler with a fully-populated RequestContext.

There’s one platform database for metadata (company records, admin users, audit log) and one isolated database per company for all event, ticket, order, and attendee data. The platform DB connection details are environment variables. Company DB tokens are encrypted in the platform DB and decrypted per-request.

  • Deno 2 / TypeScript on Bunny Edge Scripting
  • LibSQL (SQLite) via Bunny Database
  • Zero external dependencies beyond jose (JWT) and @libsql/client
  • Strict Content-Security-Policy, X-Frame-Options, and HSTS on every response
Architecture
┌───────────────────────────────┐ │ Bunny Edge Script (main.ts) │ │ Deno/TypeScript · Global PoPs │ └─────────├───────────├───────────┘ ┌─────────┘ ┌──────────────┐ │Platform │ │ Company A DB │ │DB │ │ Company B DB │ │metadata │ │ Company N DB │ └─────────┘ └──────────────┘
2 Per-request tenant resolution

Every request knows exactly which company it’s for

On every incoming request, the Edge Script reads the Host header and looks up the matching company in the platform database — either by custom_domain or subdomain.

The result is cached per-host for 5 minutes to avoid a platform DB round-trip on every request. Once resolved, the company’s AES-256-GCM encrypted DB token is decrypted in memory and used to create a per-request LibSQL client pointed at that company’s isolated database.

  • Host-based routing — each subdomain or custom domain resolves to one company
  • 5-minute in-memory cache per host — minimal platform DB load
  • AES-256-GCM decryption of DB token per request — never stored in plaintext
  • Unknown or unprovisioned hosts return 404 before any DB call
Request flow
1
Read Host headere.g. tickets.acme-events.com
2
Platform DB lookupMatch custom_domain or subdomain → company row
3
Decrypt DB tokenAES-256-GCM decrypt db_token_enc in memory
4
Create LibSQL clientPoint at the company’s isolated Bunny Database
5
Build RequestContext{ db, tenantId, companyId, company } passed to handler
6
Route to handlerMethod + path matched, response returned with security headers
3 Provisioning

New client live in under 10 seconds

Creating a new company is a single API call to POST /api/platform/companies. The platform handles everything: it creates an isolated Bunny Database, runs the full schema, encrypts and stores the connection token, seeds an admin user, and returns one-time credentials.

The client receives a login URL, an admin email, and a temporary password. They log in to the admin dashboard, set up their branding, connect their payment account, and create their first event. From provisioning to first ticket sale can happen in under an hour.

  • Bunny Database API called to create isolated DB
  • Full company schema applied immediately
  • DB token encrypted with AES-256-GCM before storage
  • PBKDF2-hashed admin password seeded
  • Company status transitions: provisioningactive
Provisioning sequence
1
Insert company rowStatus: provisioning
2
Call Bunny Database APICreate new isolated LibSQL database
3
Encrypt & store DB tokenAES-256-GCM encryption with platform key
4
Run company schemaAll tables created with IF NOT EXISTS
5
Seed admin userPBKDF2-hashed password, first login forced
6
Status → activeReturn one-time credentials to operator
✅ Company live in < 10 seconds
4 Configuration

Client configures. You do nothing.

Once provisioned, the client logs in to their admin dashboard. They set their brand colours, upload a logo, configure their email sender domain, and connect their own Stripe or SumUp account. None of this requires a deployment or any change on your end.

All branding and payment configuration is stored in the client’s own isolated database. The ticketing site re-reads it on every page load. Changes are live immediately.

  • Brand config stored per-tenant — immediate effect
  • Payment keys encrypted at rest per company — never visible in the UI
  • Mailgun domain and API key configured independently per client
  • Admin can invite additional staff users with limited permissions
  • Check-in tokens are separate credentials — safe to hand to venue staff
Admin dashboard — Settings
Brand colour
#1a56db
Custom domain
tickets.riverside-events.co.uk
Payment provider
Connected Stripe · acc_Riverside***
Email sender
hello@riverside-events.co.uk

Security that doesn’t need explaining to clients.

Every layer is locked down — from database isolation to webhook signatures to password hashing.

🔒

AES-256-GCM at rest

Every company’s database token is encrypted with AES-256-GCM using a platform-level key stored as an environment variable. Tokens are decrypted in memory per request only.

🔐

PBKDF2 password hashing

Admin passwords are hashed with PBKDF2-SHA256 and a random salt. Plaintext passwords are never stored or logged. Brute-force is computationally expensive by design.

🆕

HMAC webhook verification

Every incoming payment webhook is verified against the provider’s HMAC signature before any database operation. Unverified payloads are rejected with a 400 response.

Idempotency keys

Checkout and mutation endpoints accept Idempotency-Key headers. Safe to retry on network failure. Duplicate requests return the cached response without re-processing.

📋

JWT authentication

HS256 JWTs with 24-hour expiry for admin sessions. Check-in tokens are scoped to a specific event. Platform admin tokens are a separate key entirely.

🚫

Rate limiting

Per-IP rate limiting on all public endpoints. Configurable thresholds per route. Abuse is blocked at the edge before it reaches the database.

🌐

Security headers

Every response includes Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security. Configured at the handler level, not bolted on.

📄

Audit log

Every admin action is recorded in a tenant-scoped audit log: who, what, when, from which IP. Immutable. Non-repudiation baked in.

Fast everywhere. No configuration required.

Built on Bunny’s global edge network. No region selection, no warm-up, no capacity planning.

🌐

Global PoPs

Bunny Edge Scripting runs at Bunny’s global network of Points of Presence. Requests are handled at the nearest PoP to the user — automatically, without any configuration.

No cold starts

Edge Scripts are always warm. There are no Lambda-style cold start delays, no container spin-up times, no gradual warm-up periods. Every request gets the same response time.

📌

Per-host caching

Tenant resolution results are cached per hostname for 5 minutes. A busy event page doesn’t hammer the platform database on every page load.

What you actually need to run this.

The infrastructure footprint is deliberately minimal. You need Bunny, a payment provider, and an email service. That’s it.

  • Bunny.net account Edge Scripting for the API + Bunny Storage for frontend assets + Bunny Database for platform and company databases + Bunny DNS (optional).
  • 💳
    Stripe or SumUp (per client) Each client company connects their own payment account. You don’t need a central payment account — clients set up their own.
  • 📧
    Mailgun (per client) Each client configures their own Mailgun API key and sending domain. Transactional emails send from their domain. You can also use a shared Mailgun account with multiple sending domains.
  • 📌
    A few environment variables PLATFORM_DB_URL, PLATFORM_DB_TOKEN, PLATFORM_ENCRYPTION_KEY, JWT_SECRET, BUNNY_API_KEY, and a few more. Set once, never touched again.
# Required environment variables PLATFORM_DB_URL # Platform DB endpoint PLATFORM_DB_TOKEN # Platform DB auth token PLATFORM_ENCRYPTION_KEY # 32-byte AES key (base64) PLATFORM_JWT_SECRET # Platform admin JWT secret PLATFORM_HOST # e.g. events.qwikr.events JWT_SECRET # Company admin JWT secret BUNNY_API_KEY # For DB provisioning BUNNY_DB_REGION # DE / NY / LA / SG MAILGUN_API_KEY # Transactional email MAILGUN_DOMAIN # Sending domain # Generate PLATFORM_ENCRYPTION_KEY: openssl rand -base64 32
Request deployment guide →

Seen enough? Let’s get you started.

We’ll walk you through the deployment and have your first company live within the hour.

See pricing → Talk to us