Shared event β organizer guide β
β οΈ Screenshots: No local environment is available to capture live screenshots without deploying external infrastructure. This guide describes the actual implemented behavior. Screenshots will be added in a follow-up once a staging environment is provisioned.
A shared event lets one organizer-owned concert be distributed by two or more independent organizations β each selling against the same real-world performance at the same venue, with shared physical capacity, while keeping its own sellers, sales, customers, and detailed reports. You create the event, invite participants, control the capacity policy, manage permissions, and see the combined picture.
Your role β
Your organization is the organizer organization. You (and any other users you designate) are SharedEventOwners β named users with full authority over the shared event. This authority is distinct from your regular organization admin role: only named owners and users you explicitly grant capabilities to can act on the shared event itself. There must always be at least one owner.
A platform super admin can also create a neutral shared event with no organizer organization and act as the first owner.
Creating a shared event β
Navigate to Management β Shared events β New shared event to start the creation form. You supply:
- Name, date, and description β the canonical identity shared by all participants.
- Showtimes β one or more performances, each stored as an absolute UTC instant with a venue timezone (so DST transitions never shift a showtime). Capacity is enforced per showtime, not event-wide.
- Ticket-type templates β the price templates used by all participants. Prices are canonical on the shared event; no participant can set their own prices.
- Capacity policy β choose quota (per-organization allocations) or pool (one finite shared total, first-come-first-served). See Capacity policy below.
- Shared physical capacity β the total number of seats per showtime.
The event is created in draft status. You can invite organizations and finalize configuration before publishing.
Inviting participant organizations β
You invite participant organizations using single-use invitation links. Generate a link from the shared event detail page, share it securely with an admin of the target organization, and they accept on behalf of one of their own organizations. The same organization cannot appear more than once on the roster; you can have two or more participants.
Platform super admins can also add organizations by searching for them directly by name. For all other roles, invitation links are the only supported invitation method.
Once an admin uses the link to accept, their organization immediately joins the roster. You cannot sell against a participant's projection until acceptance is complete and the projection is provisioned. The roster statuses you will see are:
| Status | Meaning |
|---|---|
invited | Invitation sent; awaiting response |
accepted | Admin has accepted; projection being provisioned |
active | Projection ready; selling is live |
declined | Organization declined the invitation |
left | Organization chose to leave |
removed | You (or another owner) removed them from the roster |
A participant in left or removed status keeps its committed sales and historical reports but cannot make new sales. Before a participant can leave or be removed, any external channel they own must be disabled or reassigned, and their owner/grant entries are revoked (see Cancellation behavior).
Generating an invitation link β
Open the Participants panel on the shared event detail page and select Generate invitation link. The form includes:
- Email (optional) β Enter an email address to have the platform send the link directly. Choose the language (English or Norwegian) for the email.
- The generated link appears with a Copy button. Copy and share it with the intended organization admin.
Treat the link as a secret. Anyone who holds the link can join an organization to this shared event. Do not post it publicly or in open channels. If you accidentally share it with the wrong person, revoke it immediately and generate a new one.
About email delivery: After you submit, the platform returns a generic confirmation regardless of whether the email address is valid, belongs to a registered user, or is associated with any organization admin. This is intentional β no response reveals account or organization information. The link is also returned to you directly, so you can copy and forward it manually if email delivery does not reach the recipient.
In-app notification (optional side effect): If you provide an email address, the platform may also create an in-app notification in the recipient's notification bell β but only if that email address matches a known, approved organization admin on the platform. This lookup and any resulting notification are not observable by you: the confirmation response is identical whether or not a match was found, and whether or not a notification was created, delivered, read, or acted upon. Do not treat the generic confirmation as delivery confirmation for either email or in-app channels. If you need to ensure the recipient is aware of the invitation, share the link directly through a trusted channel.
The link expires after 14 days.
Managing and revoking links β
The Participants panel lists your generated links with their status, creation date, expiry date, and whether email delivery was attempted:
| Status | Meaning |
|---|---|
active | The link is valid and has not yet been used |
used | An admin accepted the link; the organization is on the roster |
revoked | You revoked the link; it is permanently unusable |
To revoke an active link, select Revoke. A revoked link cannot be un-revoked; generate a new link if the target admin still needs to join.
Quotas after acceptance: In quota mode, quota allocations for a newly accepted organization are not set automatically. After the organization's status becomes active, assign a quota for each showtime in the capacity settings.
Capacity policy β
Quota mode β
In quota mode you assign a fixed per-showtime allocation to each participating organization. Enforcement is per organization per showtime: concurrent buyers within one organization can never commit above its quota. No house reserve exists in v1 β the sum of per-showtime quotas must equal the shared physical capacity exactly. You cannot save a quota configuration that does not sum to capacity.
What quota mode means for you as organizer:
- Each participant's remaining allocation is independent; one organization selling out does not affect the others.
- You can rebalance quotas (see Editing capacity and quotas).
- If one organization has unsold quota when the event approaches, you can redistribute it before the event.
- The combined remaining shown in reporting is the sum of participants' remaining allocations (a display figure; the per-organization enforcement gate is separate per org).
Pool mode β
In pool mode there is one finite shared total per showtime. Any participating organization can sell the last seat; sales are first-come-first-served across organizations. The pool is finite β it is not unlimited inventory. The remaining shown in the UI is the authoritative figure used for sold-out decisions.
How the pool works:
- A buyer's checkout reserves seats (holds them against the total). Held seats count toward remaining and prevent overselling.
- When the order is confirmed, the hold converts to a committed sale.
- If a checkout is abandoned, the hold expires automatically (the exact duration is configurable) and the seats return to the pool.
- Retries using the same reservation identity return the existing hold without booking extra seats.
What pool mode means for you as organizer:
- No per-organization caps; any org can sell the last seat.
- No per-participant ceiling means one fast-selling org can absorb most of the remaining capacity; consider whether quotas suit your event better.
- Remaining displayed is the shared pool remaining (
capacity β held β committed).
Editing capacity and quotas β
You can edit total showtime capacity and per-organization quotas (quota mode) using the capacity edit controls. All edits use a freeze-then-apply guard: while an edit is being applied, new purchases receive a brief, retryable "capacity is being updated" response. No committed sale is lost or double-counted during an edit.
Constraints:
- Capacity cannot be reduced below
committed + heldfor a showtime. - In quota mode, quotas must still sum to capacity and each quota must be at least that org's committed sales after the edit.
- Increasing capacity in quota mode requires allocating the added seats to one or more organizations in the same edit.
Policy transitions β
You can switch between quota and pool mode after setup using Management β Shared events β [event] β Capacity policy β Change policy.
Quota β pool:
- All committed sales are preserved.
- The pool opens with remaining equal to
capacity β sum(committed per org). - Rejected if committed sales already exceed physical capacity (resolve the overcommitment first).
Pool β quota:
- Each supplied quota must cover that organization's committed sales (
quota β₯ committed). - Quotas must fully allocate the venue (
sum(quota) == capacity; no house reserve). - The transition waits for any active reservations to commit or expire before completing; no valid in-flight purchase is cancelled.
- Rejected if invariants cannot be satisfied.
Every transition is concurrency-guarded (two transitions cannot both proceed), audited with the from/to policy, and rejected β never forced β when invariants cannot be met.
Permissions β
Owners β
Owners have full authority over the shared event. You can add and remove owners from the Permissions panel using the Owners section. At least one owner must always exist; removing the last owner is rejected and you must assign a replacement first.
Owner changes are audited.
Capabilities and role templates β
You can delegate granular capabilities to named users from any active participating organization. Available capabilities:
| Capability | What it allows |
|---|---|
manageSharedDetails | Edit shared name, date, description, showtimes |
managePricing | Edit shared ticket-type prices |
manageCapacity | Edit capacity, quotas, and trigger policy transitions |
manageParticipants | Invite, remove, and manage participants |
manageExternalSales | Assign and operate external sales channels |
viewCombinedReports | Access the combined cross-org report |
manageOwners | Add and remove owners (owner-only; not grantable to non-owners) |
You can also apply role templates that expand to a predefined capability set:
| Template | Capabilities granted |
|---|---|
owner | All capabilities |
eventManager | manageSharedDetails, managePricing, manageCapacity, manageParticipants, manageExternalSales |
reportViewer | viewCombinedReports |
Important rules:
- Being an admin of a participating organization does not automatically confer shared-event authority.
- A grantee must belong to an
activeparticipant organization; grants to users outside the roster are rejected. - Grants become inactive when a grantee's organization leaves or is removed; the grant is automatically revoked on exit.
- All grant and owner changes are recorded in the audit trail.
Shared external sales channels β
Each external ticket source (for example a Ticketmaster manual entry channel or a Billetto automation channel) is assigned to exactly one responsible participating organization per shared event. Use the External channels panel to assign, update, or disable a channel.
How channel assignment works:
- Only an owner or a
manageExternalSalesgrantee can assign or reassign a channel. - The responsible organization must be an
activeparticipant; you cannot assign a channel to a non-active organization. - Only the designated organization's authorized users (plus platform super admins) can log or operate that source.
- External sales from the designated channel count against that organization's quota (quota mode) or the shared pool (pool mode).
- If you reassign a channel to a different organization, the change applies to future sales only; historical attribution does not move without an explicit migration.
- A participant's exit is blocked while it owns an active channel; the channel must be disabled or reassigned first.
Credential storage: Channel credentials are stored as Azure Key Vault secret URI references only. Raw API keys, OAuth tokens, and connection strings are never stored in the application database.
β οΈ Billetto automation runtime deferred to #242. The app records the channel assignment and Key Vault credential reference. The Billetto OAuth flow, webhook ingestion, attendee/refund synchronization, and provider runtime operations are not yet implemented. Manual Ticketmaster entries are fully functional. See Not yet implemented.
Combined reporting β
Open the combined report from Management β Shared events β [event] β Sales report. As an organizer (or a viewCombinedReports grantee), you see:
- Event-level totals β capacity, committed, held, remaining, revenue, fees across all participants.
- Per-participant breakdown β totals per organization (never customer-level detail).
- External channel totals β volume per source, aggregated.
- Per-showtime breakdown β capacity, committed, remaining per showtime.
In quota mode, the reported remaining per participant is the remaining allocation for that organization. In pool mode, the reported shared remaining is the authoritative pool figure.
Privacy boundary: The combined report never exposes another organization's customer-level or detailed sales data. You see totals and breakdowns, not individual buyer records from other organizations.
Audit feed β
Every governance-relevant change is recorded in an append-only audit trail, visible in the Audit tab on the shared event detail page. The audit records:
- Roster transitions (invite, accept, decline, leave, remove)
- Owner additions and removals
- Grant issue, change, and revoke
- Capacity-policy changes (with from/to policy)
- Cancellation events (started, completed, failed)
- External channel assign, update, reassign, and disable
- Capacity and quota edits
- Reconciliation repairs
Each entry shows the action, the actor's organization, the target organization (when applicable), and the timestamp. The audit trail is append-only; no entry is ever edited or deleted.
Cancellation behavior β
Set the event status to Cancelling from the event detail page. The cancellation saga runs these steps:
- A permanent capacity fence is established β no new reservations can be created from this point.
- The shared event root transitions to
cancellingstatus. - All currently held (not yet committed) reservations are drained; their held seats are released.
- All participant projections are locked so no new sales can occur.
- The shared event transitions to
cancelled.
What cancellation does and does not do:
- β Stops all new reservations and sales immediately.
- β Drains all in-flight holds (returning held seats; there are no more available seats anyway).
- β Locks all participant projections so no further sales can be placed.
- β Preserves all committed sales, their revenue records, and the full audit history.
- β Does not refund customers. The app has no customer refund workflow in v1. Committed sales are final; the capacity-accounting entries for a committed sale are not reversed.
If the cancellation saga is interrupted (for example by a process restart), the maintenance timer resumes it automatically from the last safe checkpoint.
No-refund semantics: Ticket purchases are final in v1. There is no customer refund request or workflow. Capacity is released in only two exceptional paths: whole-event cancellation (which drains held seats but does not refund committed sales) and authorized administrative correction of an erroneous sale. Neither path is a customer refund; the app does not support that workflow. The broader external-provider integration is designed to ingest provider cancellation and refund state when #242 implements it.
Error and retry states β
Capacity changing (retryable 503): During a capacity edit or policy transition, new purchases receive a brief retryable response ("capacity is being updated, try again shortly"). This is not a failed purchase; the buyer should retry and will succeed once the guard clears.
Sold out: When remaining reaches zero (pool mode) or an organization's quota is exhausted (quota mode), further purchases for that scope are rejected as sold out. In pool mode, the shared remaining never goes below zero. In quota mode, other organizations' allocations are unaffected.
Capacity fence conflict: If a cancellation is in progress and a purchase arrives at exactly the same time, the purchase is rejected with a non-retryable conflict response. This is expected behavior during cancellation.
Maintenance recovery: The maintenance timer monitors stale cancellations and stale capacity holds. If a saga was interrupted mid-step, the timer resumes it idempotently. No manual intervention is required for standard failures.
External channel overbook: If an external import (for example a delayed Ticketmaster manual entry) exceeds remaining capacity, it is accepted and recorded; the resulting overbooking is flagged in the capacity view and shown as a negative remaining. An alarm is raised to the designated organization and the SharedEventOwner. This mirrors how standalone events already handle external oversell.
Not yet implemented β
The following items are not yet implemented and are outside the scope of the current shared events module. They will be addressed in future work.
| Item | Deferred to |
|---|---|
| Billetto OAuth credential resolver and token refresh | #242 |
| Billetto webhook ingestion and attendee sync | #242 |
| Billetto-driven refund and cancellation state ingestion | #242 |
| Provider runtime (attendee records, cancellation events, refund synchronization) | #242 |
| Customer refund workflow for committed sales | Not planned for v1 |
| Bulk/CSV import of external sales | Not planned for v1 |
| Retroactive adoption of an existing standalone event into a shared event | Not planned for v1 |
| Cross-organization sale-milestone notifications to other organizations' admins (not related to in-app invitation notifications, which are implemented) | Post-v1 |
| Full concurrency load-gate on Azure (T050) | Blocked: isolated Azure Cosmos approval |
Related pages β
- Shared event β participant guide
- Data model β shared events
- Architecture β shared events
- Admin guide
- Issue #76 specification:
specs/076-cross-organization-shared-events/spec.md