Demo Fjord

How this demo is wired

For developers: the operator-side calls behind this site

1. The session is minted on the server

The tenant API key stays in this server. Before it draws a page it calls POST /v1/widget/sessions on the operator API with { "external_user_id": "..." }, and the page gets only the returned token, which it passes to PredictionMarketWidget.mount({ sessionToken }). A signed-in demo account mints for its own player; the studio preview, which has no login, mints for a blocked helper user. A visitor who has not logged in gets an anonymous session, minted with { "anonymous": true } on every page load: the widget shows the markets and their live prices, refuses every trade, and its log-in button calls onLoginRequest, which opens this site's login.

Demo shortcut: one token is reused across visits until 6 minutes before it expires. The integration guide says to mint a new token on every page load; do that in production. Getting started

2. Players and balance stay with the operator

A sign-up registers the player with PUT /v1/users/{external_user_id}. The Deposit page plays the operator's cashier: it credits the player with POST /v1/balance-sync ("direction": "credit") and an Idempotency-Key made when the form is drawn, so a resubmitted form replays the first credit instead of paying twice. The header balance is read on the server from GET /v1/users/me with the player's widget token.

"Try as demo player" signs in one shared player per site. Its first sign-in registers it and credits 100.00 through the same balance-sync call under a fixed key, so it is credited once.

3. Widget callbacks

  • onDepositRequest({ marketSlug, stake }): the player pressed Deposit on a stake above the balance. This site opens its deposit page with the stake, rounded up, already filled in, and links back to the market after the credit.
  • onPositionOpened and onPositionClosed: the header balance is read again at once.
  • onReady: called once, when the widget first draws a view from loaded data. This demo does not use it; an operator would remove its own loading placeholder there.
  • onRouteChange with hostNavigation: true: the site owns the address bar, so /m/{slug} and /portfolio survive a reload.

Widget reference

4. Webhook receiver

The platform posts signed events to /webhooks on this site. The receiver checks X-Webhook-Signature, which is sha256= and the hex HMAC-SHA256 of the raw body under the webhook secret, before it parses anything, answers 401 to a signature that does not match, and recognises a retried delivery by X-Webhook-Delivery. Without a secret it answers 503, so the platform keeps the delivery and tries again. The newest 200 deliveries are on the webhook log.

Webhooks

5. Theme from the card studio

The page reads the tenant config (GET /v1/widget/config) with a session of its own and paints its header, page colour, font and corner radius from the theme saved in the card studio. A visible page asks /site/config every 10 seconds and applies the look again when the config changes, so a saved theme shows without a reload. Inside the studio, unsaved edits arrive by postMessage.

6. Same-origin proxy

The widget calls relative URLs, so this server forwards /v1/*, the realtime socket /connection/* and the bundle, and no CORS allowlist is involved. Demo note: the bundle comes from /widget/v0/widget.iife.js; the integration guide documents the published /widget/v1/ path. Realtime