> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kinematic.run/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the customer docs for kinematic, a name written in lowercase except at the start of a sentence. Kinematic provides provably fair casino games, served to a casino's site by a game server: a Cloudflare Worker that the kinematic dashboard deploys into the casino's own Cloudflare account.
> Never put SESSION_SECRET or WALLET_SECRET in code, prompts, logs, commits or chat. Read them from environment variables and ask the person you work with to set them. The dashboard's Integration page shows each secret once.
> Values such as lucky-star, luckystar.example, og-rgs-lucky-star.luckystar.workers.dev, games.luckystar.example and player_48213 are examples. Use the casino's own casino ID, game server origin, element tag, session issuer and currencies: the Instructions for your coding agent on the dashboard's Integration page hold them, or ask the person you work with.
> To debug, start at https://docs.kinematic.run/troubleshooting and https://docs.kinematic.run/reference/errors. The wallet contract is at https://docs.kinematic.run/api/wallet.openapi.yaml.
> Sandbox bets are recorded on Solana devnet. Live game servers record nothing on Solana until the audited mainnet program is deployed.

# Troubleshooting

> Fixes for the problems integrations meet: blocked loaders, origins, unauthorized tokens, refused bets, wallet signatures and redirects, deploys on your own hostname, reports, Solana records and the switch to live.

Start with the browser's developer tools on the page with the games: the **Console** names CSP refusals and errors, and the **Network** tab shows the game server's answers. Every code on this page is explained in [Errors](https://docs.kinematic.run/reference/errors.md).

## The browser refuses the loader or the connection

The console names the directive or the cause. Each refusal has one fix:

| Console | Fix |
| - | - |
| `Refused to load the script '.../element/v1/loader.js'` and `script-src` | Add your game server's origin to `script-src`. |
| `Refused to connect to 'wss://...'` or `'https://...'` and `connect-src` | Add your game server's `https` and `wss` origins to `connect-src`. |
| `Refused to compile or instantiate WebAssembly` | Add `'wasm-unsafe-eval'` to `script-src`. The Provably fair dialog needs it. |
| `Refused to apply inline style` | Add `style-src 'unsafe-inline'`, or the games render without their styles. |
| `Refused to load the image 'data:...'` | Add `data:` to `img-src`. Keno needs it. |
| `Refused to load the font` | Add your game server's origin to `font-src`. |
| A CORS error on `loader.js` or the catalog, and `403` in the Network tab | Your game server answered `Origin denied`. See [Origin denied](#origin-denied). |

[Content Security Policy](https://docs.kinematic.run/integrate/csp.md) has the full set of directives.

### Origin denied

Your game server answers `403 Origin denied` to any request whose `Origin` is not in **Allowed origins**, under **Players and sessions** in [Settings](https://docs.kinematic.run/dashboard/settings.md#players-and-sessions). The match is exact: scheme, host and port.

* `https://luckystar.example` and `https://www.luckystar.example` are different origins. Add each one your pages use.
* `http://localhost:5173` is allowed only in sandbox. A live casino accepts `https` origins only.
* Saved origins reach your game servers within about a minute.

## The game stays on Connecting

The element shows **Connecting** when it cannot open its connection to your game server. Browsers hide the reason from the page, so look in the Network tab at the request to `/v1/stream`:

* **No request, or an error before it.** Your session endpoint failed. The `onError` callback of `connect()` receives your endpoint's error, `token_timeout` after 10 seconds, or `token_invalid` when the answer is not a token. Check that the endpoint answers the token as plain text.
* **`401`.** The game server refused the token. See [The game server answers unauthorized](#the-game-server-answers-unauthorized).
* **`403`.** The page's origin is not allowed. See [Origin denied](#origin-denied).
* **`503`.** The game server has no settings yet. Wait a minute after the first deploy.

The element keeps retrying, and asks your endpoint for a new token each time, so a wrong secret shows as repeated calls to your session endpoint.

## The game server answers unauthorized

Every token problem gives the same `unauthorized`. Decode the token your endpoint answered and check each claim:

```bash theme={null}
node -e 'const [h, c] = process.argv[1].split(".").slice(0, 2).map((p) => JSON.parse(Buffer.from(p, "base64url"))); console.log(h, c, "expires in", c.exp - Math.floor(Date.now() / 1000), "seconds")' "$TOKEN"
```

Then check the signature with the secret your backend uses:

```bash theme={null}
node -e 'const { createHmac } = require("node:crypto"); const [h, c, s] = process.argv[1].split("."); console.log(createHmac("sha256", process.env.SESSION_SECRET).update(h + "." + c).digest("base64url") === s ? "signature matches" : "signature does not match")' "$TOKEN"
```

The usual causes:

| Check | Common mistake |
| - | - |
| Header | The JWT library leaves out `"typ": "JWT"`, or signs with another algorithm than `HS256`. |
| Signature | The token is signed with `WALLET_SECRET` instead of `SESSION_SECRET`, or with the secret base64-decoded. The key is the secret's text as it is. |
| `sub` | The player ID has an `@`, a dot or a colon. Use 1 to 128 letters, digits, `_` or `-`, such as your internal account number. |
| `iss` | It differs from the issuer in **Settings**, even by a trailing slash. |
| `tenant` | It is not your casino ID. |
| `currency` | The currency is not one of your currencies, or the claim is missing while you serve several. |
| `exp` | It is in milliseconds instead of seconds, in the past, or more than an hour ahead. Check your server's clock too. |
| `cnf` | The key is changed on the way. Pass the element's `publicKey` through unchanged. |

A `409` with `request_failed` instead means the game server received something that is not a JWT: check that your endpoint answers only the token, with no quotes or JSON around it.

## Bets are refused

| Code | Cause and fix |
| - | - |
| `bet_limit` | The stake is outside your limits, or the bet's largest possible payout is above your maximum payout. Crash and HiLo by hand are checked at 1,000,000.00×. See [Maximum payout and the largest multiplier](https://docs.kinematic.run/reference/games.md#maximum-payout-and-the-largest-multiplier). |
| `bets_disabled` | New bets are stopped on the **Game server** page. Select **Accept new bets again** there. |
| `insufficient_funds` | Your wallet declined the debit. If the player has the funds, check that your wallet reads `amount` in the currency's minor units: `"7500"` is 75.00 at 2 decimal places. |
| `game_disabled`, `currency_disabled` | The game or the currency is off on **Games**. |
| `round_active` | The player has an open round of the same game played by hand. It must finish first. |
| `player_key_changed` | The player signed from a new device and the token carries no `cnf`. Add the device key to every token. See [Device keys](https://docs.kinematic.run/integrate/session-tokens.md#device-keys). |
| `backpressure`, `settlement_backpressure` | Your wallet is not keeping up. See [Bets stay open](#bets-stay-open). |

## Bets stay open

When your wallet's answers fail, the game server keeps each call and sends it again with the same ID and body: after 1, 2, 4, 8 and 16 seconds, then every 30 seconds. The bets stay open, the game shows settlement as delayed, and new bets are refused with `settlement_backpressure` once the queue backs up. The **Live** page counts the codes under **Errors in the last 24 hours**, each with a sentence; nothing reaches the player as an error.

Fix your wallet's answer and the queued calls go through on the next attempt. The codes:

* **`wallet_unavailable`** with a `3xx` status. Your wallet URL redirects, for example from `http` to `https`, from the bare domain to `www`, or to add a trailing slash. The game server never follows redirects. Set the wallet URL to the address that answers directly.
* **`wallet_unavailable`** with `401` or `403`. Your signature check refused the call. See [Your wallet refuses the signature](#your-wallet-refuses-the-signature).
* **`wallet_unavailable`** with `404`. The game server appends `debit`, `credit` and `balance` to your wallet URL. Check that the URL is the prefix of your three endpoints.
* **`wallet_timeout`.** Your wallet took longer than 8 seconds.
* **`wallet_receipt`.** Your receipt does not repeat `id` and `currency`, or `balance` and `revision` are numbers instead of strings of digits.

### Your wallet refuses the signature

Compute the signature exactly as the game server does:

1. Read the raw request body as bytes, before any JSON parsing. A body parsed and serialized again does not match.
2. Join the `X-Kinematic-Timestamp` header, a dot and the raw body: `1793606400123.{"id":"batch_...",...}`.
3. HMAC-SHA256 it with your `WALLET_SECRET`, taken as text, and compare the lowercase hex digest with `X-Kinematic-Signature` in constant time.
4. Allow 60 seconds between the timestamp and your clock. Keep your servers' clocks synchronized.

Run **Check my wallet** on **Integration** in live mode: it reports the exact HTTP status and receipt problem.

## The deploy fails on your own hostname

A game server on your own hostname needs its domain to be an active zone in the same Cloudflare account, with no DNS record for that hostname. [Choose an address](https://docs.kinematic.run/dashboard/game-server.md#choose-an-address) explains why.

The form checks the hostname before it deploys and shows its refusal under **Hostname**:

| The form says | Fix |
| - | - |
| `No zone in <account name> holds <hostname>. Add its domain to this Cloudflare account, or start on workers.dev.` | Add the domain to that Cloudflare account, or choose the account that holds it. A domain whose DNS is elsewhere must move its nameservers to Cloudflare first. |
| `The zone <zone> is <status> in Cloudflare. Point its nameservers at Cloudflare and wait until the zone is active, or start on workers.dev.` | Finish the nameserver change at your registrar and wait until Cloudflare marks the zone active. |
| `A game server already serves this hostname. Choose another hostname.` | Another game server of your casino already uses it. Choose another hostname, or use that game server. |

If Cloudflare refuses the hostname during the deploy, the game server's card shows one of these sentences, with **Read the fix** linking here and **What Cloudflare said** holding Cloudflare's exact error:

| The card says | Fix |
| - | - |
| `A DNS record for <hostname> already exists, so Cloudflare cannot attach your game server to it. Delete that record in the zone's DNS settings and deploy again.` | Cloudflare's code `100117`. Delete the record for that exact hostname in the zone's DNS settings, then select **Deploy again**. Cloudflare creates the record the game server needs. |
| `<hostname> is attached to another Worker in your Cloudflare account. Remove it from that Worker's domains and deploy again.` | Cloudflare's code `100116`. In Cloudflare, remove the hostname from the other Worker's domains, then select **Deploy again**. |
| `Cloudflare could not attach <hostname> to your game server. Its domain's DNS must be on Cloudflare in the same account, the zone must be active, and the hostname must have no DNS record of its own. Fix that and deploy again.` | Check the three conditions against **What Cloudflare said**, fix the one that fails, then select **Deploy again**. |

For a game server that never deployed, each sentence adds `, or remove this game server and create one on another hostname` before its final full stop. If you cannot move your domain's DNS to Cloudflare, use the workers.dev address instead.

## There is no workers.dev subdomain

An account gets its workers.dev subdomain the first time someone opens **Workers & Pages** in its Cloudflare dashboard. Without one, the form says:

`This account has no workers.dev subdomain yet. Open Workers & Pages once in its Cloudflare dashboard to create one, or use your own hostname.`

A deploy that fails for the same reason shows on the card:

`This Cloudflare account has no workers.dev subdomain yet. Open Workers & Pages once in its Cloudflare dashboard to create one, then deploy again.`

Open **Workers & Pages** once in that Cloudflare account, then select **Deploy** in the form, or **Deploy again** on the card. You can also deploy on [your own hostname](https://docs.kinematic.run/dashboard/game-server.md#your-own-hostname) instead.

## The game server does not report

Each game server sends the dashboard a report about once a minute. The game server's card on the **Game server** page shows its state:

* **Waiting for its first report**, then **No report yet** when it deployed more than 5 minutes ago and has not reported. The card says `It has not reported since it was deployed <time ago>. Check now asks it why.`
* **Not reporting** after 5 minutes without a report. The card says `It sent its last report <time ago>. Check now asks it at once.`
* **Deploying**, **Deploy failed** or **Not deployed**: the game server does not run yet. See the card's note.

Once every step of the launch checklist on **Home** is done, Home also shows `Your game server <host> sent its last report <time ago>.` with **Open game server** when a game server has not reported for 5 minutes.

Select **Check now** on the card. Its answer names the cause:

* **The game server does not answer.** The card shows why, for example `Your game server could not be reached.` or `Your game server did not answer in time.`
* **It refuses its settings.** A line starts with `Settings:`, such as `Settings: The settings are for the other mode. A mode switch takes effect when you redeploy the game server.` Most refusals are fixed by redeploying. See [Game server settings](https://docs.kinematic.run/reference/errors.md#game-server-settings).
* **It cannot reach the dashboard.** A line such as `Settings: Your game server could not reach the dashboard.` or `Reports: Your game server could not post its report to the dashboard.` These clear on their own once the connection returns.

A game server that runs a release from before the live monitor still reports, but the **Live** page shows `<host> runs a release from before the live monitor, so its bets show here once you update it.` Update it on the **Game server** page with **Update to** and the release, or leave automatic updates on.

## Bets wait to be recorded on Solana

Sandbox game servers record each bet on Solana devnet shortly after its proof is disclosed. Until then, the Provably fair dialog shows the bet as waiting to be recorded. When bets keep waiting, the **Solana record** section of the **Game server** page shows **Paused** under **Recording**, with one of these sentences:

* `Its address cannot pay the next transaction's fee. Recording resumes within a minute after the address is funded.`
* `Bets wait for a new VRF key to be registered on Solana. It is registered automatically within 10 minutes, or you can register it now.` To register it at once, select **Plan key registration**, then **Register key**.
* `The Solana RPC failed. Your game server tries again every few seconds.`

The **Waiting** row shows how many bets wait and how long the oldest has waited. See [Solana record](https://docs.kinematic.run/dashboard/game-server.md#solana-record). Bets keep settling and paying while they wait.

Live game servers record nothing on Solana until the audited mainnet program is deployed. That is expected, and every other proof check works.

## Sandbox and live mismatches

* **You switched to live, and bets still use test balances.** Each game server keeps running in its mode until you redeploy it, and refuses the live settings with `control_mode` until then: **Check now** shows `Settings: The settings are for the other mode. A mode switch takes effect when you redeploy the game server.` **Settings** shows `One game server still runs in sandbox mode. It switches to live mode when you redeploy it.` with **Open game server**, and the game server's card says `This game server runs in sandbox mode and your casino is set to live. Redeploy it to switch.` Select **Redeploy in live mode** on each card. Automatic updates never switch modes.
* **Saving live settings fails on origins.** Live accepts `https` origins only. Remove `http` and `localhost` origins.
* **Tokens fail after the switch.** Your live currencies differ from your sandbox ones, for example `TEST` replaced by `USD`. Sign tokens with a currency your casino serves now.
* **Check my wallet does not run.** It runs only in live mode, with your wallet URL set.
* **A player shows an old test balance in live.** The player's ID played in sandbox on the same game server, which keeps the last balance it saw for that ID until your wallet reports a newer revision. Use test player IDs that no real player has, such as `test-` followed by a number.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.