Skip to content
View Markdown

Loading documentation…

Stage state and live updates

Share display state with a stage screen and subscribe to changes.

Access and state

Stage reads and streams are public. Anyone with the stage URL can read its state; keep attendee details, credentials and private organizer notes out of this payload. Writes use organizer authentication, company rights of at least 1 and a matching event/stage pair. See permissions.

A stage is a complete client-defined JSON object stored in cache. Its id and event_id must match the write route and current event. Additional display/session fields are preserved; there is no fixed agenda schema. For a new default stage, use the event UUID as the stage UUID. Other stage IDs must already exist and belong to that event.

Read a stage

Method and path Response
GET /stages/load_stage/{stage_id} {"stage":{...}}.
GET /stages/storage/{stage_id} The stage object directly.

These paths do not require an event prefix or a .json suffix. Missing state returns {"error":"No stage found"} with HTTP 200. Check the body, not just the status.

Write a stage

POST /{event_id}/stages/ce/{stage_id} initializes or replaces an authorized stage. Submit the whole state under stage; success returns {"status":"ok"}.

curl --fail-with-body --user "$PAYDRO_EMAIL:$PAYDRO_PASSWORD" \
  --request POST --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"stage":{"id":"11111111-1111-4111-8111-111111111111","event_id":"11111111-1111-4111-8111-111111111111","title":"Main stage"}}' \
  "$PAYDRO_ORIGIN/$EVENT_ID/stages/ce/$EVENT_ID"

POST /{event_id}/stages/storage/{stage_id} replaces an existing stage and echoes the submitted state. If it does not exist, the action returns the HTTP 200 error before writing. Both writes replace the complete object; omitted display fields are removed. Re-read state after saving. There is no version precondition or patch merge; coordinate concurrent writers to avoid overwriting each other.

Receive live updates

GET /stages/sse/{stage_id} returns text/event-stream. In a browser, use EventSource and parse message data as JSON. On a different origin, browser access also depends on the server's CORS policy; prefer a same-origin stage screen or server-side relay.

const stream = new EventSource(`/stages/sse/${stageId}`);
stream.onmessage = ({ data }) => {
  const message = JSON.parse(data);
  if (message.stage) renderStage(message.stage);
};
stream.addEventListener('close', () => stream.close());
stream.onerror = () => showConnectionStatus('Reconnecting');

Changed state arrives as {"stage":{...}}. Idle messages contain {"state_not_changed":true}, or {"ping":true} when state is absent. Ignore these heartbeat objects. The server pads frames with whitespace and ends the stream after roughly ten minutes with a named close event. To keep a display connected, create a new stream after that event with bounded backoff; close it when the screen is removed. This stream carries stage state and is not an order/payment webhook feed.

Verification

State and stream behavior is traced to the stage controller. Authorization tests cover editor access, foreign state and rejected methods. Continuous streaming and concurrent state replacement still require an integration test in your stage environment. Use synthetic test data for that check.