Seat States & Hold Types
Seatmap.pro tracks every seat in every event through a two-layer state model. The first layer is a small closed set of lifecycle states that the booking engine uses for concurrency and availability. The second layer is an open, customer-defined holdType that carries the business context – why is this seat held, who can release it, how long does it last, and how should it look on the seatmap.
The two layers
| Layer | Column | Values | Controls |
|---|---|---|---|
| Lifecycle | state |
closed enum | concurrency, availability |
| Hold type | holdType |
open string | TTL, release policy, payment, display |
The booking engine only ever asks state = 'ACTIVE' to determine whether a seat can be locked. The holdType is metadata that governs the business flow around that seat.
Lifecycle states
Four states, fixed by the platform.
| State | Meaning | Bookable | TTL applies |
|---|---|---|---|
ACTIVE |
Available for selection | Yes | No |
LOCKED |
Held, pending action | No | Per hold type |
SOLD |
Committed and fulfilled | No | No |
BLOCKED |
Operationally unavailable | No | No |
A typical cart flow moves a seat ACTIVE → LOCKED → SOLD. An abandoned cart moves it back LOCKED → ACTIVE. A broken seat sits in BLOCKED until operations clears it.
Hold types
The holdType is a string you attach to a seat when it transitions out of ACTIVE. It lives alongside the lifecycle state and describes the reason the seat is held.
Platform defaults
Two hold types are shipped out of the box:
| Hold type | Valid state | TTL | Released by | Payment |
|---|---|---|---|---|
CART |
LOCKED |
15 minutes | customer, admin, system | required |
RESERVED |
LOCKED |
none | admin | not required |
CART is the default for the v2 booking /lock endpoint – omitting holdType in the request body resolves to CART.
Defining your own hold types
Every organization can declare additional hold types via the JSONB config column on organization (and optionally inherit from the parent tenant). Configuration is read through the standard org/tenant config API used for other features like webhooks.
Example payload sent to POST /api/organizations/config/:
{
"holdTypes": {
"PARTNER_HOLD": {
"validStates": ["LOCKED"],
"ttlSeconds": 3600,
"releaseApi": "ADMIN,API",
"paymentRequired": true,
"displayName": "Partner Hold",
"displayColor": "#8B008B"
},
"COMP": {
"validStates": ["LOCKED", "SOLD"],
"ttlSeconds": null,
"releaseApi": "ADMIN",
"paymentRequired": false,
"displayName": "Complimentary",
"displayColor": "#FFD700"
}
}
}
Field reference:
| Field | Type | Description |
|---|---|---|
validStates |
string array | Which lifecycle states this hold type is valid for. The booking service rejects requests that combine an invalid pairing. |
ttlSeconds |
integer or null | Auto-release window. null means the hold persists until explicitly released. |
releaseApi |
string | Comma-separated list of actors allowed to release: CUSTOMER, ADMIN, SYSTEM, API. |
paymentRequired |
boolean | Whether this hold must be followed by payment to convert to SOLD. |
skipLockedState |
boolean | When true, transitions directly ACTIVE → SOLD (used for box office flows). |
displayName |
string | Human-readable label shown in admin UIs. |
displayColor |
string | Hex color for renderer styling. |
displayIcon |
string | Optional image or icon reference for the renderer. |
Tenant-level inheritance
If your organization belongs to a tenant, you can define hold types at the tenant level once and they apply to every organization in the tenant. Per-organization overrides take precedence field-by-field, so an organization can, for example, shorten the CART TTL without restating the full definition.
Resolution order for any hold type lookup:
- Organization-level
config.holdTypes[name] - Tenant-level
config.holdTypes[name] - Platform defaults
The resolver merges these three layers. If you override only ttlSeconds at the organization level, the rest of the definition is inherited from the tenant or the platform default.
Using hold types in the booking API
The v2 booking StateSelection payload accepts an optional holdType field:
POST /api/private/v2.0/booking/lock?eventId=123e4567-e89b-12d3-a456-426614174000
Content-Type: application/json
{
"sessionId": "abc123",
"seats": [{ "id": 42 }],
"holdType": "PARTNER_HOLD"
}
The booking service validates:
- The hold type is defined (as a platform default, tenant-level entry, or organization-level entry) for the calling organization.
validStatesincludes the target lifecycle state.
Unknown or mismatched hold types return 400 Bad Request. The /unlock and /revertsale endpoints do not take a holdType – returning a seat to ACTIVE clears whatever hold was attached.
Concurrency and the conditional lock
Every state transition is a conditional SQL update that asserts the expected current state in its WHERE clause. Two concurrent requests locking the same seat cannot both succeed – the second request’s update affects zero rows and the API returns false. This makes double-booking impossible at the database layer, regardless of cart flow timing.
Audit trail
Every successful state transition appends an immutable row to seat_state_events, recording:
- Old and new lifecycle state
- Hold type at the moment of the transition
- Session id (when available)
- Seat or group-of-seats affected
- Timestamp
This append-only log is the source of truth for analytics, replacing the best-effort telemetry that preceded it. Downstream reporting can answer questions like “what percentage of CART holds converted to SOLD in the last week” without touching the booking hot path.
Renderer integration
Today, the booking renderer styles seats based on their lifecycle state (ACTIVE, LOCKED, SOLD). Custom holdType values are returned by the API and included in seat payloads, but a dedicated renderer.setSeatsState(seats, holdType) helper for applying customer-defined visuals is on the roadmap and not yet shipped. Until then, customers who want to visualize custom hold types can use the existing onBeforeSeatDraw callback to read the hold type from the seat data and apply their own fill or overlay.
Related
- Section States & Styling – the equivalent concept applied to section outlines (highlighted, selected, unavailable, filtered).