A clear foundation.
A considered next step.
The event workspace, its architecture and the steps needed to bring your payment, maps and email connections online.
Implemented 14 October 2026. This release extends the existing Clearspan builder. It is the first phase of the supplied master brief, not the complete three-phase product. External provider and mobile-browser acceptance gates remain open.
1. Existing architecture and interface
The module reuses React 19, Vinext, Cloudflare Workers, D1, R2, Sites-managed ChatGPT identity, the saved website workspace and its brand values. It adds no parallel authentication system or independent application. The existing account-free builder and template store remain available.
Open Events and community in the workspace or visit /event-manager. Organiser and staff access requires sign-in; public booking does not. Desktop uses a sidebar, mobile uses compact navigation. Forms use drawers with focus management and inline errors. Light and dark preferences are local to the browser.
| Area | Working actions |
|---|---|
| Overview | Real booking, revenue, attendance and repeat-customer totals, upcoming events and outstanding tasks |
| Events | Create, save, edit, preview, publish, cancel, complete, duplicate and bulk duplicate; search, status filters and sorting; list, day, week and month calendars |
| Bookings and tickets | Search and filter bookings, controlled exports, free cancellation, owner-authorised full paid refunds, waiting lists |
| Guest check-in | Rear-camera QR scanning, guest search, per-person manual admission, group arrival progress and event-day tasks |
| Customers | Search, first-time and returning segments, engagement and inactivity filters, notes, tags, consent withdrawal and anonymisation |
| Venues | Saved addresses, Google place selection when configured, capacity, hire cost, contact details, notes and published-event clash protection |
| Team and operations | Email-bound invitations, owner/manager/check-in roles, optional event-scoped scanners, assigned tasks |
| Revenue | Provider-confirmed gross payments, refunds, known payment fees, recorded expenses and estimated results; event comparisons and cost exports |
| Integrations | Actual configuration state, Stripe Connect onboarding/status, pending-payment reconciliation and email retries |
| Settings | Business branding, guest contact email, activity log and labelled future phases |
Advanced navigation is intentionally not presented as working functionality. Community memberships, marketing, private bookings and the AI founder assistant belong to later phases.
2. Multi-tenant data model
Migration drizzle/0004_amused_hemingway.sql adds the new tables. Previously applied migrations are unchanged.
| Record | Relationships and constraints |
|---|---|
| event_businesses | One owned business per authenticated owner in this release; JSON fallback brand; connected Stripe account |
| event_members | Business + user unique; owner, manager or checkin; optional event assignment for check-in staff |
| event_invites | Hashed single-use code; exact invited email; seven-day expiry; business, role and optional event |
| event_venues | Business-owned venue; organiser-entered address and details; optional Google place ID |
| managed_events | Business, optional saved website and venue; globally unique slug; revision and conditional write key; optional series ID |
| event_ticket_types | Event-owned ticket tiers with integer GBP prices, capacity and sale windows |
| event_bookings | Business, event, ticket type; unique event + request key; quantity, amount, terms snapshot, consent, payment and refund status; unique Stripe session |
| event_tickets | One record per guest; unique booking + ordinal; event/business boundary; check-in timestamp and actor |
| event_customers | Unique business + email; notes, tags and marketing choice |
| event_waitlist | Unique event + email; requested quantity; does not reserve inventory |
| event_costs | Business/event expense in pence, category and description |
| event_tasks | Business/event setup task, named owner and completion state |
| event_audit | Event, ticket and financial activity; deterministic IDs for retry-safe system actions |
| event_webhook_log | Unique processed Stripe event ID, connected account and type |
| event_outbox | One confirmation job per booking, state, attempts and last error |
| event_rate_limits | Short-lived hashed-IP buckets; expired entries pruned on requests |
Existing workspaces records supply saved website brands. Existing media_assets and R2 hold event photos. Event images are publicly readable only while referenced by a published, public or unlisted event. Otherwise access requires business membership. The original builder media-delete route protects photos used by events.
Every organiser operation derives the business from a verified membership, never from an untrusted owner ID. Managers operate events and CRM; only owners manage staff, payment onboarding, refunds, business settings and anonymisation. Check-in staff receive event/ticket arrival details and tasks, without customer emails, financial records or other business records. Removing membership ends future API access.
3. Event and booking lifecycle
Events begin as drafts. Publishing requires a future start, venue, description, terms and ticket tiers. Paid tiers also require an enabled Stripe account. The write atomically checks revision, venue capacity and overlapping published events in the same venue and business. Draft/private previews require owner or manager access; unlisted pages are public by link and excluded from indexing.
Weekly and monthly recurrence creates up to 12 independently editable drafts with a shared series ID. Venue-local time is preserved across daylight-saving changes; nonexistent local times are rejected. Monthly dates clamp to the last valid day. Custom schedules, whole-series editing, course entitlements and automatic reschedule notices are deferred. Tier sale windows are explicit per event and should be reviewed on every recurring draft.
Bookings reserve inventory in a single conditional SQL insert. Both event and tier capacities count pending and confirmed quantities. Booking closure, event status, tier price and sale windows are checked inside that insert. Repeated client request keys return the existing booking only when the guest and ticket details match. Simultaneous retries do not duplicate a reservation.
Free reservations become confirmed immediately. Paid reservations remain pending until the server verifies payment. A paid hold is never released solely because a local timer elapsed. An expired provider session releases it. A missing session response is recovered using Stripe idempotency or a bounded provider session search. After 90 minutes, a completed provider search with no matching session can close a failed checkout; incomplete or failed searches retain the hold for review.
Ticket definitions remain locked once a booking exists, including refunded/cancelled history. Dates and venues are locked while bookings are active. Revisions prevent stale overwrites. Cancelled events reject new bookings and scans. Cancellation does not automatically notify guests or refund them; the UI explicitly asks the organiser to review those actions. Returning an event with active bookings to draft is blocked.
4. Payments and confirmation
Stripe Connect Standard accounts receive direct charges through Stripe Checkout. All money is integer GBP pence. Card details remain with Stripe. There is no application fee, manual payout mechanism, subscription billing or platform-fee configuration in this release.
Checkout uses a stable booking-specific idempotency key and server-calculated amounts. Connected-account webhooks at /api/events/stripe/webhook verify the raw payload signature and five-minute timestamp tolerance. The server retrieves the Checkout Session and PaymentIntent from Stripe, checking connected account, metadata, booking reference, currency, total and successful amount received. Only then does it confirm the booking and issue QR tickets. Browser redirects do not prove payment.
Webhook processing is retry-safe, with unique event logs and conditional state transitions. Refund/dispute notifications re-fetch the charge before updating the ledger. Full refunds require explicit owner confirmation. Pending refunds are reported as pending until provider confirmation. Disputed tickets are blocked. Known original processing fees are recorded when Stripe provides an expanded balance transaction; missing fees are labelled. Estimates subtract refunds, recorded costs, known fees and disputed amounts; they are not accounting statements or payout balances. Dispute resolution, fee adjustments and settlement reconciliation remain manual/provider-side.
Confirmation jobs are persisted before delivery. Resend uses a booking-specific idempotency key. Provider acceptance marks a message sent; it does not prove inbox delivery. Failed, queued or interrupted jobs can be retried from Integrations. No scheduler or automatic backoff worker is deployed. There are no marketing sends, reminders or waitlist offers yet. Guests retain immediate access to private QR tickets even when email is not configured.
5. Secure QR check-in
Each guest ticket has a random UUID and an HMAC-SHA256 signature made with the server-only ticket secret. QR payloads contain no name or email. Booking access uses a separate HMAC scope. Private booking tokens remain in the browser URL fragment and are submitted in the Authorization header, not query parameters. Ticket responses are no-store; pages are noindex with a no-referrer policy.
The camera uses the html5-qrcode library, only after the staff member presses Open rear camera and grants browser permission. Manual guest search is available if the camera cannot be used. HTTPS and a supported device are required. The server validates membership, assigned event, token signature, booking status and event status, then atomically changes an unchecked ticket. Concurrent scans have one winner; the second receives Already checked in. A group of four has four independently scannable tickets. Audit entries identify manual and camera admissions.
Attendance uses polling, not a push/realtime transport: the guest list polls every 10 seconds and the full workspace every 30 seconds. Successful local check-ins refresh totals immediately. Scanning pauses on each result until Ready for the next guest. Vibration is requested when supported. Audio feedback, external-provider QR codes and offline admission are not implemented. Digital tickets can be printed or saved as PDF using the browser; there is no server PDF or wallet pass.
6. Integration configuration
Use native Sites runtime environment settings and redeploy to apply changes. No real keys belong in source, a browser form, hosting.json or chat output. .env.example lists names only.
| Key | Required setup |
|---|---|
| EVENT_TICKET_SECRET | Stable cryptographically random signing key, at least 32 characters. Configured for this release. Do not rotate casually: rotation invalidates issued ticket and booking signatures. |
| EVENT_PUBLIC_ORIGIN | Exact HTTPS origin. Configured as the existing Clearspan address. |
| STRIPE_SECRET_KEY | Secret for the Stripe Connect platform. Start with test mode. Enable Connect and Standard account onboarding. |
| STRIPE_WEBHOOK_SECRET | Signing secret for a destination receiving connected-account events at the exact webhook URL. Match test/live mode. |
| GOOGLE_MAPS_BROWSER_KEY | Browser-visible restricted key. Enable Maps JavaScript, Places API (New) and Maps Embed. Restrict HTTP referrers to the exact Site domains and restrict allowed APIs. |
| RESEND_API_KEY | Server-only email credential with permission for the verified sending domain. |
| EVENT_EMAIL_FROM | Verified sender, for example Your business <tickets@your-domain.example>. |
Stripe destination: https://siteforge-studio.okeowo-d.chatgpt.site/api/events/stripe/webhook. Subscribe to checkout.session.completed, checkout.session.expired, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, charge.refunded and charge.dispute.created. Use Stripe API version 2024-06-20 for the configured destination; server requests pin the same version. Complete each business's onboarding and verify charges and payouts in Integrations. Connect eligibility and verification depend on Stripe and the business jurisdiction.
Google uses the official Place Autocomplete widget and Google map embed with provider attribution. Only place IDs are persisted from Google. Organisers separately enter the address/name they own. Publish the platform's applicable terms and privacy notices before enabling Google in production. Manual venue entry and directions work without an API key. API usage/billing remains in the configured Google project.
Email is essential booking communication only. Marketing permission is optional, unchecked initially and separate from booking terms. Reopening a ticket cannot restore withdrawn consent. No bulk marketing channel exists in this release.
Official references: Stripe direct charges, Stripe webhooks, Checkout Sessions, Google Place Autocomplete, Places policies, Resend email API, html5-qrcode.
7. Phased implementation plan
Phase 1 delivered in source: native event workspace, persistent tenant/role model, wizard, image upload, recurrence, calendars, venue conflicts, branded public pages, free bookings, paid Checkout adapter and verified webhooks, private QR tickets, online scanning, waiting lists, CRM, operational tasks, basic finance, invitations and audit history.
Phase 1 activation gates: configure Stripe, Google and email; complete the real provider sandbox journey; test camera permissions and simultaneous admissions on iPhone and Android; review responsive/keyboard layouts, branded pages, download and print output. These gates are not marked passed by mocked tests. No live sale has been created.
Phase 2: add a durable scheduled-job runner with retries and delivery webhooks first, then reminders, reviewed reschedule/cancellation notices and consent-aware waitlist offers. Introduce credit/entitlement ledgers before memberships, passes, loyalty and referrals. Build private-event enquiries, quotations and deposits on the verified payment ledger. Add materials, resource allocation and staff shifts. Give the founder assistant tenant-scoped read tools and reviewable drafts; require confirmation for sends, refunds and publication. No AI provider is activated by this release.
Phase 3: provider-backed wallet passes and external-ticket adapters; opt-in galleries and moderated community tools; chapter/brand hierarchy, location reporting and custom domain routing; venue plans constrained by real capacity; offline tickets with encrypted device storage, expiry and reconciliation. Disconnected scanners cannot guarantee globally unique admission until they sync. Add forecasting only after sufficient actual data, with labelled uncertainty.
Platform-wide subscriptions, tenant administration, feature entitlements, service fees and platform analytics need a separate privileged control plane and are not exposed as tenant settings. One owned brand per account, with membership in other businesses, is the current limit.
8. Code map
db/events-schema.tsanddrizzle/0004_*: normalised data and migration.lib/events/model.ts: validation, money, date and CSV helpers.lib/events/server.ts: identity, tenant access, bound queries, HMAC, rate limits and brand inheritance.lib/events/management.ts: organiser actions, venue conflicts, roles, exports and aggregates.lib/events/booking.ts: inventory reservation, payment reconciliation, ticket creation and atomic admissions.lib/events/providers.ts: Stripe, webhook verification and confirmation outbox delivery.app/api/events/**: authenticated management, public booking, private tickets, webhook and media endpoints.components/events/**: organiser workspace, wizard, calendar, public booking, tickets and scanner.app/events.css: scoped responsive styles, dark dashboard and print ticket styles.tests/events.mjs: real SQLite migration/API integration tests plus controlled provider fixtures and server renders.
9. Test and deployment checklist
Automated checks cover the migrated relational schema, real persistent writes, anonymous rejection, same-origin mutations, cross-business isolation, invitation email binding, least-privilege staff, capacity races, concurrent retry keys, stale edits, concurrent venue publication, capacity reduction, historical booking references, consent withdrawal, waiting lists, per-guest check-in, duplicate/wrong-event/refunded scans, Stripe signature and amount verification, repeated webhooks, refunds, financial totals, provider recovery, tasks, DST recurrence, CSV formula escaping and public/organiser renders. Stripe is mocked only inside the test process. Existing builder/workspace/landing regressions are separate checks.
Run node tests/events.mjs, node tests/workflows.mjs, node tests/guest-workspace.mjs, node tests/landing.mjs and node node_modules/typescript/bin/tsc --noEmit. Use the Sites source helper for the production build, exact-source commit/archive and native deployment. Migration 0004 is additive; do not change applied migration history. Preserve the existing public audience.
Before enabling paid production events:
- Verify deployed migration, blank tenant onboarding, durable event creation and branded public page.
- Configure Stripe test credentials and the connected-account webhook. Finish organiser onboarding. Confirm test mode is visibly labelled.
- Create a paid event, reserve the last places concurrently, pay, receive one confirmation and open one QR per guest. Refresh and replay webhooks without duplicates.
- Test abandoned checkout, expired session, lost network response, delayed webhook, failed email and explicit reconciliation. Capacity must remain safe.
- Scan on iPhone Safari and Android Chrome. Test camera denial, two staff devices, duplicates, wrong event, partial groups, removed staff, cancellation and refund.
- Verify Google picker, attribution, saved place ID, directions, restricted key and published terms/privacy pages.
- Verify transactional email with a real inbox, sender authentication, private links and retry behaviour. Provider acceptance alone is not delivery proof.
- Review keyboard focus, narrow screens, dark mode, image upload visibility, PDF print layout and screen-reader status messages.
- Review payment/refund policies, configured email sender, data retention and operational incident process, then switch both Stripe key and webhook to live mode together. Do not treat a test payment as a live sale.
Known limits: GBP/card-only; no coupons, transfers, gift tickets, instalments or membership gates; online-only scanning; 500 events per business, 10 tiers per event, 10 guests per booking, 12 generated recurring drafts; lists/exports currently cap at 500 records and guest search at 150 matches. There is no automatic retention cleanup, scheduled email runner, bounce tracking, platform error-monitoring service or tested backup/restore runbook. Customer anonymisation is owner-controlled and blocked by active bookings; payment-provider records follow provider retention. Minimise personal data in notes. Financial and audit records remain after anonymisation. Formal privacy compliance and commercial readiness require the operator's policies and the live acceptance gates above.
No supported browser-control skill was available in this environment, so interactive visual QA and real camera tests were not performed. Automated renders and a successful build do not replace them.