Ticketing & Events
The ticketing vertical lets a cultural organisation (a festival, a concert series, an
association) publish a programme of dated events and sell tickets for them — and lets a developer
embed that catalogue and buy flow into the organisation’s own website (e.g.
amei-musique-en-vacances.viite.ai). It reuses the platform’s billing and Stripe foundations:
ticket revenue settles directly to the organiser’s Stripe account via Stripe Connect.
The model
Section titled “The model”Event ──< Session ──< TicketTariff the catalogue an organisation publishesOrder ──< OrderItem a buyer's purchase- Event — e.g. XXXIème Festival “Musique en Vacances”. Has a
slug(unique per organisation, the public read key), astatus(draft→published→archived), and an optional date window. - Session — a dated show within an event (e.g. Samedi 18 juillet 18h00 – Récital de piano).
- TicketTariff — a priced, inventoried ticket type under a session. Three
kinds:fixed— a set price (e.g. €10).pay_what_you_want— the buyer chooses, optionally above a floor (minAmountCents).free— registration only; no payment, the order is confirmed instantly. A tariff may cap inventory (capacity);available/soldOutare derived. VAT is recorded per tariff (vatRateBps, e.g.2000= 20%).
- Order — created by an anonymous buyer (no platform account). Inventory is reserved when the order is created; a paid order is confirmed when Stripe Checkout completes, a free order immediately. Money is always stored in minor units (cents).
1. Connect a Stripe account (organiser, once)
Section titled “1. Connect a Stripe account (organiser, once)”Ticket payments use Stripe Connect — funds settle to the organiser’s own account. From the
dashboard Events page, the organiser clicks Connect Stripe, which runs Stripe’s onboarding.
Until the connected account reports chargesEnabled, paid tickets cannot be sold (free RSVP
tariffs still work). Programmatically:
const { url } = await platform.events.connectOnboard({ returnUrl: 'https://dashboard/events' });// redirect the organiser to `url`; poll status afterwards:const status = await platform.events.connectStatus(); // { connected, chargesEnabled, ... }2. Author the programme (organiser)
Section titled “2. Author the programme (organiser)”In the dashboard, or via the authenticated SDK (platform.events.*):
const event = await platform.events.createEvent({ title: 'Musique en Vacances', status: 'published' });const session = await platform.events.createSession(event.id, { title: 'Samedi 18 juillet 18h00 – Récital de piano', startsAt: '2026-07-18T18:00:00Z',});await platform.events.createTariff(session.id, { name: 'Tarif Prévente', kind: 'fixed', priceAmountCents: 1000, capacity: 200,});Only published events are visible on the public surface.
3. Render the catalogue on your own site (public, API-key gated)
Section titled “3. Render the catalogue on your own site (public, API-key gated)”Mint an organisation-scoped API key (dashboard → Settings → API keys, see API keys) and keep it server-side. Then read the published catalogue:
import { PlatformApiClient } from '@abeauvois/platform-sdk';
const site = new PlatformApiClient({ baseUrl, getToken: () => process.env.VIITE_API_KEY });
const events = await site.events.listPublicEvents(); // listconst detail = await site.events.getPublicEvent('musique-en-vacances'); // sessions + tariffs// detail.sessions[].tariffs[] carry { id, name, kind, priceAmountCents, available, soldOut, ... }The public DTO is a whitelist — internal ids, ownership, and inventory counters never leak; only
available/soldOut are exposed.
4. Sell a ticket (public buy)
Section titled “4. Sell a ticket (public buy)”The buy endpoint is anonymous (gated only by the organisation API key), mirroring quote-request submission. Pass the buyer’s email, the tariff lines, and your own success/cancel URLs:
const { order, checkoutUrl } = await site.ticketing.createOrder({ eventSlug: 'musique-en-vacances', buyerEmail: buyer.email, items: [{ tariffId, quantity: 2 }], successUrl: 'https://your-site/tickets/ok', cancelUrl: 'https://your-site/tickets',});
if (checkoutUrl) { // Paid order → redirect the buyer to Stripe Checkout (charge settles to the organiser). window.location.href = checkoutUrl;} else { // Free order → already confirmed (order.status === 'confirmed').}Inventory is reserved the moment the order is created and released automatically if anything
fails (organiser not yet payable, Stripe error) or the checkout is abandoned. A confirmed order can
be polled with site.ticketing.getOrder(order.id).
Source attribution
Section titled “Source attribution”Pass source/linkSlug (e.g. the ?s=/utm_source value your site captured from a tracked
link) alongside the order to get a fully source-attributed paid event — no separate integration
needed:
const { order, checkoutUrl } = await site.ticketing.createOrder({ eventSlug: 'musique-en-vacances', buyerEmail: buyer.email, items: [{ tariffId, quantity: 2 }], successUrl: 'https://your-site/tickets/ok', cancelUrl: 'https://your-site/tickets', source: trackedSource, linkSlug: trackedLinkSlug,});Both fields are persisted on the order and echoed through Stripe Checkout metadata; on
checkout.session.completed the webhook emits an analytics paid event carrying the same
source/linkSlug, so /analytics’s per-source report includes paid conversions — unlike a plain
HelloAsso payment link, whose webhook never relays query params back into paid-event metadata.
Configuration
Section titled “Configuration”| Variable | Purpose |
|---|---|
STRIPE_SECRET_KEY | Shared Stripe secret (reused from subscriptions). |
STRIPE_TICKETING_WEBHOOK_SECRET | Signing secret of the ticketing webhook endpoint (/api/ticketing/webhooks/stripe). |
TICKETING_APPLICATION_FEE_BPS | Optional platform commission per sale, in basis points (0 = none). |
The webhook keeps orders and Connect status in sync: checkout.session.completed confirms an order
(and commits its inventory), checkout.session.expired releases an abandoned one, and
account.updated tracks when an organiser becomes payable.
