Skip to content

Ticketing & Events

The ticketing vertical lets a cultural organisation (a festival, a concert series, an association) publish a programme of dated events and take registrations for them — and lets a developer embed that catalogue and order flow into the organisation’s own website (e.g. amei-musique-en-vacances.viite.ai).

Event ──< Session ──< TicketTariff the catalogue an organisation publishes
Order ──< OrderItem a buyer's purchase
  • Event — e.g. XXXIème Festival “Musique en Vacances”. Has a slug (unique per organisation, the public read key), a status (draftpublishedarchived), 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/soldOut are 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 free order is confirmed immediately, and a priced one is refused (409) with its inventory released. Money is always stored in minor units (cents).

1. Connect a Stripe account (organiser, once)

Section titled “1. Connect a Stripe account (organiser, once)”

Nothing in ticketing charges a card today, so this step is optional for events — it exists for the organiser’s other Connect surfaces (offers, invoicing), and is what paid tickets will use when they return. From the dashboard Events page, the organiser clicks Connect Stripe, which runs Stripe’s onboarding; chargesEnabled is what gates selling Offers and issuing invoices. 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, ... }

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 '@viite-ai/platform-sdk';
const site = new PlatformApiClient({ baseUrl, getToken: () => process.env.VIITE_API_KEY });
const events = await site.events.listPublicEvents(); // list
const 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.

The order endpoint is anonymous (gated only by the organisation API key), mirroring quote-request submission. Pass the buyer’s email and the tariff lines:

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',
});
// Free order → already confirmed (order.status === 'confirmed').
// `checkoutUrl` is always null: there is no payment step while ticketing is
// unpriced. A priced order never reaches here — it fails with 409.

successUrl/cancelUrl are still accepted and still ignored, so the call keeps its shape for when paid tickets return.

Inventory is reserved the moment the order is created and released automatically if anything fails — including the 409 a priced order gets, so a refused sale never holds seats. A confirmed order can be polled with site.ticketing.getOrder(order.id).

Pass source/linkSlug (e.g. the ?s=/utm_source value your site captured from a tracked link) alongside the order, so a registration carries where its buyer came from:

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. They no longer feed /analytics’s per-source paid conversions — that path ran through Stripe Checkout metadata and the ticketing webhook, both of which went with paid ticketing. Revenue events now reach the funnel through the analytics webhook (a HelloAsso payment link, say), and the order’s own source/linkSlug remain queryable on the order itself.

VariablePurpose
STRIPE_SECRET_KEYThe one shared Stripe secret. Not used by ticketing itself while it is unpriced — only by the organiser’s Connect onboarding.

There is no ticketing webhook any more: /api/ticketing/webhooks/stripe went with the payment step, as did STRIPE_TICKETING_WEBHOOK_SECRET and TICKETING_APPLICATION_FEE_BPS (a platform commission only makes sense for a platform that takes the money). Connect onboarding status arrives on /api/connect/webhooks/stripe (account.updatedchargesEnabled), which gates selling Offers and issuing invoices.