Security model
Rolink never sees a player’s private keys. Players prove which wallet is theirs by signing a message in their own wallet, and Rolink keeps only public addresses. The secrets that do exist are your project’s: an API key for your game servers, your Roblox OAuth app’s client secret, an optional Open Cloud key for live events, and the managed wallet that signs your transactions. This page explains how each one is protected, what can go wrong, and what’s left to you.
What’s at stake
Section titled “What’s at stake”| Secret or asset | Where it lives | If it leaks |
|---|---|---|
Project API key (rlk_live_...) | A Roblox Secret in your experience. Rolink keeps only a SHA-256 hash. | Anyone can call the API as your project: read its links and on-chain data, use its quota, and send transactions within your policy until you revoke the key. |
| Managed wallet key | Inside Turnkey’s secure enclaves. It never leaves them. | Not exportable, so it can’t leak from Rolink or from your game. What it can sign is decided by requests that pass your policy. |
| Open Cloud API key | Encrypted at rest in Rolink’s database (AES-256-GCM). | Anyone can publish messages to your experience’s servers, including fake Rolink events. |
| Your dashboard wallet | Your own Solana wallet. Rolink knows only its address. | Whoever controls it can sign in to your Rolink dashboard and manage your projects. |
Threats and mitigations
Section titled “Threats and mitigations”A leaked project API key
Section titled “A leaked project API key”- Stored as a Roblox Secret.
HttpService:GetSecretreturns an object that scripts can send in a request header but can’t read or print, and the Secret’s domain setting restricts which hosts it can be sent to. Restrict it to the Rolink API’s host. See Set up Roblox. - Server-only SDK.
Rolink.newassertsRunService:IsServer()and refuses to run on a client. Never put the key in a LocalScript,ReplicatedStorageor anything a client can see. - Shown once, stored hashed. The dashboard shows a new key once. Rolink stores its SHA-256 hash and its first 16 characters, so you can tell keys apart, and can never show the full key again.
- Revocable. Revoke a key in the dashboard under API keys, and it stops working within about 30 seconds. A project can have up to 10 active keys, so you can create a new one, move your Secret to it, then revoke the old one. Deleting a project revokes all its keys.
Even with the key, a caller can’t exceed your transaction policy or the beta quotas, can’t change the project’s settings, policy or keys (those need a dashboard session), and can’t create or remove links (only players can, on the linking page). What it can read is limited to the project’s own links, its transaction requests and its public details (name, slug, cluster, wallet address, linking page URL), plus data that is already public on-chain.
Bugs and exploits in your game code
Section titled “Bugs and exploits in your game code”A loop that pays on every frame, or a RemoteEvent that lets a client choose the amount, would drain a wallet as fast as the game can call it. Rolink bounds this:
- Idempotency keys make every payment happen at most once. Build them from what you’re paying for, not from random values. See Send transactions.
- Per-transaction caps limit each payment, and only listed mints can be sent or minted at all. SOL transfers are off until you set a maximum.
- Hourly limits cap how often one wallet, or one player across all their wallets, can be paid per rolling hour.
- Hourly budgets cap the total per asset per rolling hour across everyone. This is the limit that bounds total loss.
- Atomic checks. Limits are checked and the key reserved in one step, under a per-project lock, so concurrent requests from many servers can’t overshoot together.
Your part: decide amounts and recipients on the server, validate everything that arrives through RemoteEvents, and never let a client name a mint, an amount or an address.
The managed wallet
Section titled “The managed wallet”Each project’s wallet is created in Turnkey, and its private key is generated and kept inside Turnkey’s secure enclaves. Rolink stores the wallet’s ID and public address. To send a transaction, Rolink builds it, asks Turnkey to sign it, and checks the returned signature against the wallet’s public key before sending it.
Rolink’s API holds the credentials that let it request signatures for project wallets, and it only requests one for a transaction that passed your policy. The funds in a managed wallet therefore depend on Rolink’s own security as well as on yours. Treat it as a hot wallet:
- Keep only what the game needs in it, and top it up in small amounts.
- Set an hourly budget for every asset you allow.
- Think twice before making it a mint authority: your API key can then mint that token up to your policy’s limits.
- Watch it. Add its address as a watched address to see every transaction it signs. See Watch addresses.
One project’s data, one project’s key
Section titled “One project’s data, one project’s key”Rolink serves many games from one service, so every record belongs to a project:
- An API key resolves to exactly one project, and every
/v1route reads and writes only that project’s links, transactions and wallet. - Links are per project. A player who links a wallet in one game hasn’t linked it in another, and one game can’t see another’s links.
- Idempotency keys, watched addresses, quotas and usage are counted per project.
- In the dashboard, every project page checks that the signed-in developer owns the project. Anything else answers as if the project didn’t exist.
Linking a wallet you don’t own
Section titled “Linking a wallet you don’t own”To link, a player must sign a message with the wallet’s key. Rolink verifies the ed25519 signature over the exact message it issued, for the exact address it issued it to. Without the private key, there’s no way to produce it.
Phishing and replayed signatures
Section titled “Phishing and replayed signatures”A malicious site could try to get a player to sign a Rolink message, or reuse one they signed earlier.
- Sign-In With Solana format. The message’s first line names the Rolink API’s domain, and wallets that understand the format warn when it doesn’t match the site asking. It also names the Roblox account, the game, the wallet, the linking page’s URL and the cluster. See Link wallets.
- Single-use nonce. Each challenge carries 16 random bytes and is deleted the moment it’s checked, valid or not.
- Bound to one account and one game. A challenge is only accepted from the session of the Roblox account it was issued to, on the linking page of the project it was issued for.
- 10-minute expiry. The message states its expiration time, and Rolink enforces it.
- No funds at risk. The message is plain text, not a transaction. Signing it can’t move anything.
Cross-site requests and framing
Section titled “Cross-site requests and framing”- Same origin only. Every state-changing route (
challenge,verify,unlink,logout, the dashboard’s wallet sign-in, and every dashboard form) requires anOriginheader equal to the Rolink API’s origin, and the linking and sign-inchallengeandverifyroutes read a JSON body. - Cookies. Session cookies are
HttpOnly,SameSite=Lax,Secureand carry the__Host-prefix, which binds them to the API’s exact origin. - No framing. The linking page is served with
Content-Security-Policy: frame-ancestors 'none'; base-uri 'none'; form-action 'self'andX-Frame-Options: DENY, so it can’t be embedded to trick a player into clicking. - No leaks. The linking page sets
Referrer-Policy: same-origin, so other sites never receive its address, andCache-Control: no-store. It loads no third-party scripts or styles.
Sign in with Roblox
Section titled “Sign in with Roblox”Players sign in on a linking page through that game’s own Roblox OAuth app, which its developer creates in Creator Dashboard and Roblox reviews. Rolink has no Roblox app of its own.
- Authorization code with PKCE (
S256) and a randomstate, both checked on the callback. - Minimal scopes:
openidandprofile. - Token revoked right away. Rolink reads the user’s identity, then revokes the Roblox access token. It never stores it.
- Fixed destinations. After sign-in, Rolink only redirects to a page on its own origin, normally the linking page the player came from, never to another site.
- One game per session. A player session only counts on the linking page of the game whose app the player consented to. Signing in on one game doesn’t sign the player in on another.
- Encrypted client secret. Each game’s client secret is stored with AES-256-GCM and decrypted only to finish a player’s sign-in. The dashboard never shows it again.
Dashboard sign-in
Section titled “Dashboard sign-in”Developers sign in to the dashboard with a Solana wallet. The wallet address is the Rolink account: there’s no password to steal or reuse.
- Sign-In With Solana message. The message’s first line names the Rolink API’s domain, and wallets that understand the format warn when it doesn’t match the site asking. It carries a random nonce and expires after 10 minutes. Signing it is free and sends no transaction.
- Exact signature check. Rolink verifies the ed25519 signature over the exact message it issued, for the address it issued it to.
- Nothing stored before sign-in. The pending sign-in lives in a signed,
HttpOnlycookie, so the sign-in routes write nothing to the database until a signature checks out.
Sessions
Section titled “Sessions”Sessions are signed cookies (HMAC-SHA256), each with a type and an expiry: 10 minutes for a pending sign-in, with Roblox or with a wallet, 1 hour for a player, 12 hours for a developer. One kind can’t be presented as another.
Forged events
Section titled “Forged events”MessagingService messages carry no signature. Anyone holding your Open Cloud key, and any server script in your experience, can publish to your event topic, so a fake wallet_linked event could briefly put a wrong address in a server’s wallet cache. That’s one reason events are hints:
- Payments are never affected. When you pay a
UserId, Rolink resolves the wallet from its own database, not from anything a game server cached. - Wallet cache entries expire after
walletCacheSeconds(60 by default). - Confirm before acting. A forged
txorwatchevent reaches yourTransactionUpdatedandChainActivityhandlers, so confirm withGetTransactionorGetSignatureStatusbefore acting on anything that matters.
Give the Open Cloud key only universe-messaging-service:publish, on this experience only.
One account per wallet
Section titled “One account per wallet”Within a project, a wallet links to one Roblox account at a time. If another account proves ownership, the wallet moves, and your servers receive WalletUnlinked for the previous account. Your game should treat the latest link as the truth and re-check with GetWallet before acting on a wallet.
What Rolink stores
Section titled “What Rolink stores”| Data | Contents | Kept |
|---|---|---|
| Developers | Wallet address, a short form of it shown as your name, first and last sign-in times | The last sign-in time is updated each time you sign in |
| Projects | Name, slug, cluster, universe ID, encrypted Open Cloud key, event topic, Roblox OAuth client ID and encrypted client secret, managed wallet ID and address, transaction policy, watched addresses | Deleting a project disables it, revokes its keys, and erases its Open Cloud key and client secret. Its links and history are kept for audit. |
| API keys | Name, first 16 characters, SHA-256 hash, creation, last use and revocation times | Revoked keys are kept, marked as revoked |
| Links | Project, Roblox user ID and username, wallet address, link time | Until the player unlinks or links another wallet, or the wallet moves to another account |
| Challenges | Nonce, project, user ID, username, address, the message text, expiry | Deleted when used. Expired ones are pruned when the next challenge is issued. |
| Transaction requests | Idempotency key, kind, a hash of the request, user ID, recipient address, asset, amount, signature, last valid block height, status, error, timestamps | Permanently: this is what makes keys work across retries and restarts |
| Watch positions | Watch name, last signature seen | Permanently |
| Usage | Requests and transactions per project per UTC day | Shown in the dashboard’s usage view |
Rolink does not store players’ private keys or seed phrases, your dashboard wallet’s keys, the managed wallet’s private key, full API keys, Open Cloud keys or Roblox client secrets in plain text, Roblox access tokens or passwords. Sessions live in the user’s own signed cookie.
Your checklist
Section titled “Your checklist”- The API key is a Roblox Secret restricted to the Rolink API’s domain, and used only in server scripts.
- Amounts and recipients are decided on the server, with deterministic idempotency keys.
- The managed wallet holds only what the game needs, and every allowed asset has an hourly budget.
- The Open Cloud key only has
universe-messaging-service:publishon your experience. - The wallet you sign in to the dashboard with is one you keep safe: whoever can sign with it can manage your projects.
- Keys you no longer use are revoked.
Report a security issue
Section titled “Report a security issue”If you find a vulnerability in Rolink, please don't post it in public. A private address for security reports is on its way. When you write, include what you found, how to reproduce it and the impact you expect.
Next steps
Section titled “Next steps”- Dashboard and projects: API keys, settings and the managed wallet.
- Send transactions: the policy and idempotency in detail.
- Use Rolink responsibly: the rules beyond security.
