Register and Launch
This is the practical path from a locally-built service to a working demo launch on Cloud1x2. At the end, the service is reachable on a public URL, registered in the admin, configured with its secrets, and serving a demo round end to end.
High-level flow
Section titled “High-level flow”sequenceDiagram participant Dev as Developer participant Svc as Game Service (built locally) participant Net as Internet (tunnel/VPS) participant CL as Cloud1x2 Admin participant PL as Cloud1x2 Platform
Dev->>Svc: build, run locally Dev->>Net: expose on a public URL Dev->>CL: New Game Service CL-->>Dev: service_uid + secrets Dev->>Svc: put secrets in .env, restart Dev->>CL: add Runtime Profile + Game CL->>PL: register PL->>Svc: desired-state push (60s heartbeat) Svc-->>PL: 200 OK (engine up) Dev->>Dev: open the demo launch URL in a browserSeven steps: build → expose → register → configure secrets → add profile + game → wait for reconcile → test.
1. Make the service reachable
Section titled “1. Make the service reachable”The platform reaches the service over HTTPS (desired-state push, runtime-profile fetch), so a public URL is required. Three options:
Tunnel (fastest, temporary)
Section titled “Tunnel (fastest, temporary)”| Tool | Command | Notes |
|---|---|---|
| ngrok | ngrok http 3030 | Free-tier URLs are short-lived; a new URL on each restart |
| Cloudflare Tunnel | cloudflared tunnel --url localhost:3030 | Free; a persistent URL is available |
| localtunnel | npx localtunnel --port 3030 | Quick, but not suited to production-like testing |
ngrok http 3030# Forwarding https://abcd-1234.ngrok-free.app -> http://localhost:3030Small VPS (persistent, production-like)
Section titled “Small VPS (persistent, production-like)”For longer testing, a small cloud server is more comfortable. Point a domain at it and front the service with Caddy or nginx + Let’s Encrypt:
your-game-svc.your-domain.com { reverse_proxy localhost:3030}Caddy handles HTTPS automatically.
Own hosting (anything goes)
Section titled “Own hosting (anything goes)”Fly.io, Railway, Render, Kubernetes, on-premise — the only requirement is that the platform can reach the service over HTTPS.
2. Register in the admin
Section titled “2. Register in the admin”In Services → New Game Service, fill in:
| Field | Example | Description |
|---|---|---|
| Slug | your-game-slug | Lowercase identifier shown in the admin (the wire identity is always the service_uid) |
| Display Name | Hi-Lo by Studio | Name shown in the UI |
| Internal URL | https://abcd-1234.ngrok-free.app | Where the platform reaches the service |
Saving produces a service_uid (16-char Crockford Base32, sent in the
desired-state payload) and a set of secrets.
Get the secrets
Section titled “Get the secrets”On the new service row, open Secrets. Four values appear:
INTERNAL_CONTROL_SECRET = <random>INTERNAL_WALLET_SECRET = <random>INTERNAL_CONFIG_SECRET = <random>INTERNAL_VERIFY_LAUNCH_SECRET = <random>These are shown once. Copy them immediately; lost secrets must be regenerated (which invalidates the old ones).
3. Configure the secrets
Section titled “3. Configure the secrets”Add to the service’s .env:
PORT=3030PLATFORM_INTERNAL_BASE_URL=https://api.dev.cloud1x2.net
INTERNAL_CONTROL_SECRET=<value>INTERNAL_WALLET_SECRET=<value>INTERNAL_CONFIG_SECRET=<value>INTERNAL_VERIFY_LAUNCH_SECRET=<value>
DATABASE_URL=postgres://your_game_app:your_game_app@localhost:5432/cloud1x2_your_gameRestart the service. At first there is no desired-state push yet (no profile or
game exists); only GET /health and GET /internal/engines (empty) respond.
curl https://abcd-1234.ngrok-free.app/health# → {"ok":true,"service":"your-game-slug","engines":{}}4. Create a runtime profile
Section titled “4. Create a runtime profile”In Runtime Profiles → New profile:
| Field | Example |
|---|---|
| Name | default |
| Engine type | resolved automatically (the service’s service_uid) |
| RTP target | 96 |
| Round wait (seconds) | 5 |
| Bot min / max | 0 / 0 |
| Demo currency | USD |
| Demo start balance | 10000 |
| Save demo bets | true |
| Config JSON | { "deckSize": 13, "winMultiplier": 1.95 } |
Config JSON is the engine-opaque config — the platform stores it and returns
it unchanged. See Engine-Opaque Config.
Saving produces a runtimeProfileId.
5. Create a game
Section titled “5. Create a game”In Games → New game:
| Field | Example |
|---|---|
| External ID | 9000 (any unused positive integer) |
| Name | Hi-Lo Classic |
| Engine type | resolved automatically (the service’s service_uid) |
| Runtime profile | default |
| Skin key | a UUID from the service’s skin catalog |
Within 1–60 seconds the platform pushes a desired-state. The service log should
show (example service_uid wp9dvdgp4wfqmxq4):
PUT /internal/engines/desired-state { "protocolVersion": "1.1", "serviceUid": "wp9dvdgp4wfqmxq4", ... }[bootstrap] service_uid set: wp9dvdgp4wfqmxq4[reconcile] +1 engine: wp9dvdgp4wfqmxq4:p:42[engine] round_open wp9dvdgp4wfqmxq4:p:42 anchor=7The engine is up and the first round has opened.
6. Test a demo launch
Section titled “6. Test a demo launch”Use Games → Hi-Lo Classic → Demo Launch (or open
https://gameplay.dev.cloud1x2.net/?gameId=9000&demo=true). In the browser:
- The frontend loads inside the iframe.
- The WebSocket connects; a
sessionevent arrives:{ ok: true, demo: true, balance: 10000, currency: 'USD', betMin: 1, betMax: 1000, ... } - A round opens (
round_openwith the anchor card). - The UI places a bet (higher / lower + 10 USD).
betPlacedarrives; the balance drops to 9990.- The round closes (
round_closewith the next card). - A correct guess pays
winAmount=19.5; otherwise0. The balance updates.
If an error appears
Section titled “If an error appears”| Error | Likely cause | Fix |
|---|---|---|
connect_error: Unknown gameId | Wrong or deleted game ID | Verify the game ID in the admin |
connect_error: Service not ready | Engine has not reconciled yet | Wait up to 60 seconds (heartbeat) |
betRejected: Service not ready | service_uid unknown | Wait for the first desired-state after restart |
| 502 / 504 from the launcher | Internal URL unreachable (tunnel closed) | Restart the tunnel; update the internal URL |
7. Real-mode test (advanced)
Section titled “7. Real-mode test (advanced)”Once demo works, real mode needs additional partner setup, done with the Cloud1x2 team: a test partner and operator, a wallet endpoint (or mock), the game added to the partner allowlist, and a bet template. A real-mode launch URL:
https://gameplay.dev.cloud1x2.net/?gameId=9000&demo=false¤cy=USD&token=test-token-123&customer=test-cust-1&organizationId=4567&trader=cx-12Next steps
Section titled “Next steps”| Goal | Page |
|---|---|
| Add skin-scoped localization | Translations |
| Manage in-game content (lists, etc.) | Content Modules |
| Understand per-round bet limits | Config + Ledger |
Common mistakes
Section titled “Common mistakes”| Mistake | Cause | Fix |
|---|---|---|
| Desired-state stops arriving | Tunnel URL changed (ngrok free tier) | Use Cloudflare Tunnel or a VPS (stable URL) |
403 Forbidden on desired-state | Wrong INTERNAL_CONTROL_SECRET | Re-copy from the admin, update .env, restart |
| Demo works, real fails | Missing partner setup | Get the wallet endpoint from the Cloud1x2 team |
| CORS error in the browser | Missing CORS header when serving the frontend | Allow the gameplay domain |
| Round never opens | The reconcile response returned 5xx | Check the service log; fix the reconciler |