Connect
Link a game player to their Discord account in a community, with confirmation from both sides.
Connect links one player of your game to one Discord member of a community on CEEJAY. The player presses a button in your game, authorises the connection with their Discord account on a page CEEJAY serves, and your game receives the member's name, avatar, level, coins and shared roles to show.
This page is for the studio's developers. It explains the flow, step by step, for each of the three ways a connection can start.
What this page covers
Connecting, staying connected and disconnecting, the button a member presses in Discord, what a connection earns, and the webhook events for a connection are here. What happens to a player afterwards (sending their data, reading them, deleting them) is on Player data. To add the button to a game, use the Connect client of the TypeScript SDK, a game kit for Swift, Kotlin, Godot or Flutter, or the recipe for any other engine. The SDKs for Unity and Unreal Engine are preview releases, currently in testing. What a connected game can show and do with the community's quests, shop and rank, including spending a member's coins, is on The game loop.
What you need
- Connect switched on. It is switched on per instance. Where it is off, every request on this page returns
404with the codenot_availableandfeature: "players": hide the button. - A game set up for the community. We set a game up for you for now; dashboard setup is coming soon. A game has a slug (such as
rugby_manager), a name and logo for the connect page, an audience, and the settings in the table below. - A public key for the game. It starts with
ceejay_pk_, ships inside the game and is not a secret. A game can have up to 5 at a time, so a new build can carry a new key before the old one is retired. - For a studio with its own server: a secret key with the
players:connectscope. A Full Access key does not include it: see Authentication.
What we save for a game, and what to send us:
| Setting | What it is |
|---|---|
| Audience | general, mixed or children. A game with a mixed audience must send underAge with every start. A game directed at children cannot use Connect; every request returns 403 |
| Web origins | Up to 10, for a browser game: https:// and a host name, or http://localhost for development. A request from a web page on any other origin is refused |
| App origins | Up to 5, for a game wrapped as an app, such as capacitor://localhost |
| Return URLs | Up to 5, each under a key you choose, such as game. An https:// URL, or a custom scheme in reverse-domain form, such as com.studio.game://connect. A request names the key, never a URL |
| Proof | The project ID of your Firebase or Unity project, or your Steam app ID, when your players sign in with one of them. And whether the game connects verified players only |
| Join URL | Where to send a player who is not in the community's server, and whether the connect page offers to join |
| Shared roles | Up to 25 roles of the server the game may be told a member has |
| Basic connections | Whether a connection where the game supplied the player ID without verification earns rewards and may spend. By default it does not. See Game rewards and The game loop |
| Game loop | Whether the game shows the community's quests, shop, locker, rank and daily reward. Off until the community's team switches it on |
| Items it may sell | The item types the game may sell, which by default is items delivered by an API, and the list of up to 200 items the team put on it. The game sells nothing until the list contains an item |
| Daily spending limit | The most coins one member can spend through the game in a UTC day: 5,000 unless the team set another number. 0 switches spending off |
| Spending hold | The hours before a new connection or device can spend: 24 unless the team set another number, from 0 to 168 |
What a game stores
| Value | Starts with | What it is | Where it travels |
|---|---|---|---|
| Public key | ceejay_pk_ | The game's own key. Not a secret. With it alone a game can start sessions and nothing else | Authorization header |
| Session secret | ceejay_cs_ | Reads the result of one connect session. Returned when the session starts | Request body |
| Refresh token | ceejay_rt_ | What a device stores in its secure store after connecting. Replaced each time it is used | Request body |
| Player session | cjps1. | A token for 15 minutes, for the requests a game makes for a connected player | Authorization header |
Each is accepted by one kind of request only and is recognised by its prefix. A secret key (ceejay_sk_) never goes into a game.
Confirmation from both sides
Nothing is saved until both sides have confirmed, each seeing the other:
- The member's consent. The member presses Connect on CEEJAY's page, having been shown the game, the player's name and the player's ID.
- The game's confirmation. The game shows its player the Discord name and avatar that were authorised, and the player confirms.
A link, a QR image or a code can be seen by someone else: on a stream, on a shared screen, on a console in a living room. Whoever opens it can authorise with their own Discord account. The game's step is where the player sees that it was not them and selects No, before anything is saved. So there is no default: ask the player every time.
Proof levels
Steam. A game on Steam verifies its player with proof: { type: "steam", token }, where the token is the hex ticket of GetAuthTicketForWebApi("ceejay") and the playerId is the player's SteamID64. CEEJAY checks the ticket with the community's publisher Web API key, saved under Connected Apps › Steam, for the app ID saved in the game's proof settings. A valid ticket gives the verified level. CEEJAY accepts each ticket once, so get a new one for every request.
A game can verify who its player is, or supply only an ID.
| Level | How the game verifies the player | What it is for |
|---|---|---|
verified | A connect pass your own server created, the token Firebase Authentication or Unity Player Authentication issued to the game, a Steam ticket, or a session your own server started | Proving the player's identity. A second device that verifies the same player is signed in with no Discord step |
basic | Nothing: the game supplies the ID. An anonymous sign-in counts as basic too | Showing the member in the game on the device that connected |
A basic connection never blocks anyone. A verified connection for the same player replaces it, and a new device or a reinstall connects again through Discord. A game can be set up to connect verified players only.
The level also decides what a device may do with a member's coins. A basic device can read, do quests and check in, but it cannot buy or claim unless the community's team allows a basic connection to spend. The game loop has the table.
The game starts it
This is the usual way in. It needs the public key and nothing on your side but the game.
The player presses Connect. If your server signs players in, it first creates a pass with Create a connect pass and passes it to the game.
The game calls Start connect with the player's ID and its proof, if it has one. The response contains url, code, qrUrl and sessionSecret. Store sessionId, sessionSecret and expiresAt immediately, where you store the refresh token: a phone often closes a game while its player is in the browser.
The game opens the link, or shows the code. See Opening the link for each kind of device. Show the code as well while waiting, so a player whose phone does not come back can finish on any device.
The member authorises the connection. In the browser they sign in with Discord, review the data that is shared, and press Connect. The page then says to go back to the game.
The game polls for the result. Call Get the result every 3 seconds, and immediately when the player returns. When status is needs_game_confirmation, the response contains the Discord name and avatar.
The game asks the player to confirm. Show "Connect to" and the name, with Yes and No, and send Confirm connect.
Connected. The response to the confirmation is the approved result: the member card, a player session and a refresh token. Store the refresh token in the device's secure store and discard the session secret.
A session lasts 10 minutes. After that the result is expired, and the button goes back to not connected.
curl -X POST https://ceejay.communitystudios.xyz/api/v1/connect/start \
-H "Authorization: Bearer $CEEJAY_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "playerId": "p_19", "playerName": "Nova77", "returnTo": "game" }'Your server starts it
Use this when your game contains no CEEJAY code, or when you want your server to make every request. Your server uses a secret key with players:connect, and never the requests a game makes: those are rate limited per IP address, and a server has one.
Your server calls Create a connect session for a player it has signed in. The secret key is the proof, so the session is at the verified level. The response contains url, code, qrUrl and sessionSecret.
Your server passes url to its game, which opens it as in Opening the link. Pass sessionSecret to the game as well only when the game should store the player's tokens.
The member authorises the connection on the connect page.
The game's confirmation. A game that has the session secret sends Confirm connect itself. Otherwise your server reads the session with Get a connect session, your game shows the player the name, and your server sends Confirm a connect session. A server that sent confirmInGame: false pre-confirmed the connection: do that only when the link is opened on the device the player is signed in on and the code is never shown where others can read it.
Your server receives the result from the webhook event player.connected, which CEEJAY sends shortly after the connection is made. Polling is the fallback: Get a connect session for one session, List connect sessions for up to 100.
If your players signed in to your game with Discord, you already know which member each player is. Skip all of this and call Link an account with the game's slug as platform. Your server creates that link without member approval, so the member sees no page, and the game gets no player session for them until they have completed Connect once.
The member starts it in Discord
A member can also start from the community's server. The community posts the game's button in a channel, and the two sides confirm each other:
The member presses the game's button. Only they see the reply: a single-use code that expires after 10 minutes, with a warning to type it only into the game and never to share it. A member who is connected already sees their player, their signed-in devices and a Disconnect button instead.
The member types the code into the game. A game sends it as code in Start connect, and a server sends it in Create a connect session. The response names the member who generated the code, and the game shows that name to its player before the player confirms. A player who was talked into typing someone else's code sees a stranger's name there.
The member presses Confirm in Discord, once the game has accepted the code. Discord then shows the same review the page shows: the game, the player's name and ID, what is shared, and Connect and Cancel. The message is visible only to the member and mentions no one.
Connected. The member's consent and the game's confirmation are both recorded, and the game polls for the result, as always.
Where the community has not posted the button, or a code was used or has expired, no pending request matches it, and code returns 404 with the code code_not_found. The code a game shows its player is a different one: it is typed on CEEJAY's page, never into a game.
Opening the link
The game always polls for the result. Returning to the game only triggers the next poll immediately, so nothing depends on a phone switching back.
| Device | Open | Notes |
|---|---|---|
| Browser game | A pop-up window, opened from the player's own click | Open a blank window in the click and set its URL when Start connect responds, so the browser does not block it. The page cannot be shown in a frame |
| Game on a computer | The system browser | |
| iPhone, Android | The system's web sign-in sheet, or a browser tab | Never an embedded web view: Discord refuses to sign in there |
| Console, television | Nothing | Show code, the URL to enter it at, and the image at qrUrl |
The URL to enter a code at is the dashboard URL of the community's instance followed by /connect. qrUrl is an SVG QR code for the link, so a console game needs no QR code of its own.
To bring the player back, send returnTo with the key of one of your return URLs. The connect page then offers "Return to the game", a plain link to that URL with ceejay=return and session (the session's ID) appended. It contains no secret.
What the member sees
The connect page is CEEJAY's, at the dashboard's URL. It shows the community's name and icon and your game's name and logo, in the language you sent as locale.
- Sign in. The game, the player's name and the player's ID, and "Continue with Discord". When the game sent no proof, the page says the game could not verify who the player is.
- Discord's own screen, which asks the member to authorise the connection.
- Review. Who is signed in, with a way to use a different Discord account. The game and the player. What the community's team will see. What the game will receive: which Discord account it is, the nickname, level, XP and coins, and the shared roles. The game's data controller. Then Connect and Cancel.
- Back to the game. "Go back to the game to finish. It will ask you to confirm" and the Discord name.
Instead of connecting, the page shows a different screen when a connection already exists:
| The page shows | When |
|---|---|
| Sign another device in | This Discord account is already connected to this player |
| Keep or move | This Discord account is connected to another player of the game. A Discord account can connect one player per game |
| Connect it here | The player is connected to another Discord account, and may be moved here. Connecting it here disconnects it there |
| Already connected | The player is connected to another Discord account at the verified level, and this session is basic. The real owner connects with proof |
| Cannot be connected | The Discord account is banned from the server, or the community blocked the player |
Where the community offers it, a player who is not in the server can tick "Also join". Joining is a separate authorisation on Discord's side.
A member manages their connections at the dashboard's URL followed by /connect/mine, with nothing but their Discord account: what is shared, which devices are signed in, and Disconnect. The game gets that URL as manageUrl.
Staying connected
A player session lasts 15 minutes. The device renews it with Renew a player session, which also replaces the refresh token. A refresh token is valid for 90 days from its last use.
On start-up a game looks, in this order, for:
- An open session it stored. Keep polling for its result.
- A refresh token. Renew.
- A proof token from its sign-in service. Call Get connection status, and start with the proof when the player is connected.
The button in your game has four states:
| State | When | The game shows |
|---|---|---|
| Not connected | No refresh token and no open session, or a renewal returned connection_ended | Connect |
| Waiting | An open session is stored. It survives a restart, and ends after 10 minutes | The code, and the confirmation question when the game's confirmation is pending |
| Connected | A valid player session exists | The member card from Get the player's profile |
| Needs attention | The renewal returned no player session, or sharing.needed is not empty | "Join the server" or "Rejoin the server" with joinUrl, or a link to manageUrl |
Three rules keep a device signed in: store the new refresh token before you use the new player session, renew one call at a time, and when a response was lost send the token you still have. Renew a player session explains why.
A second device
A connection supports up to 5 devices. A sixth signs out the least recently used device.
A new device has a hold period before it can spend a member's coins, however it was approved: see The rules about money.
- With proof. When the connection is verified and was made with the member's own consent, a new device that verifies the same player is approved immediately by Start connect:
alreadyConnectedistrue, and no one is asked. - Without proof. The device goes through Discord again. The page shows "Sign another device in", and the member sees every signed-in device on their connections page.
A player who is not in the server
The connection is saved all the same. The member card says not_member or left, no player session is issued, and joinUrl is where to send the player. The refresh token still works for Disconnect, and a renewal returns a player session once the player has joined.
Age
A game is set up with an audience, and there is no default.
- A game directed at children cannot use Connect: every request returns
403with the codeaudience_not_allowed. The two exceptions are Disconnect a player and Delete a player: a studio can always end a connection and delete a player's data. - A game for a mixed audience sends
underAgewith every start. Without it the start returns400with the codeage_answer_required. underAge: truereturns403with the codeunder_ageand stores nothing. When the player is verified, any connection they have is ended.underAge: trueon a player or an event in Set player fields or Log player events ends the connection in the same way. Because it ends a connection, the key needsplayers:connectfor it.- A connection that ended this way is not linked again by a key: Link an account returns
403with the codeunder_agefor that member and game, until the member connects again through Connect.
Disconnecting
A player can always end their own connection:
- In the game, with Disconnect and the refresh token. It works whether or not they are in the server.
- On their connections page, with nothing but their Discord account.
- Through the community's team, who can end it for them.
A member who disconnected themselves is not linked again by a key: Link an account returns 409 with the code member_disconnected until the member connects again. A studio's server ends a connection with Disconnect a player, and deletes the player data CEEJAY stores with Delete a player. Either sends player.disconnected.
From a browser game
The requests a game makes can be sent from a web page.
- The page's origin has to be on the game's list. Any other origin returns
403with the codeorigin_not_allowed. A request with noOriginheader, which is what a native game sends, passes. - Send the headers
AuthorizationandContent-Typeand no other. The response to the browser's preflight request allows exactly those two. - No cookie is read, and
Access-Control-Allow-Credentialsis never sent.
Testing on a live community
A public key can be made in test mode. A session started with a test key accepts consent only from someone who owns the server or has a role with the Administrator permission. Everyone else sees that the page is not available, so a studio can try the whole flow on a live community without touching its members.
Limits
The requests a game makes are counted per IP address, and the Connect requests of a server per game. Rate limits has the numbers.
The two API files
/openapi.jsondescribes the requests a secret key makes: the five Connect ones, and the eight about players and the field map./openapi-connect.jsondescribes the seven requests of Connect that a game makes, and the eleven requests of the game loop.
After a player is connected
Once connected, the player can earn rewards, sync data and trigger webhook events:
- Rewards. A community can grant a reward for connecting, and for the events a game logs. CEEJAY's bot grants them shortly afterwards, and the result's
rewardshows the reward status. See Game rewards. - A role. A community can assign the connection role to connected members. The role is removed when the connection ends.
- Player data. Your server sends a player's fields and events with Set player fields and Log player events, reads a player with Get a player and List players, and settles the field names with the community in the field map. See Player data.
- Webhook events.
player.connectedandplayer.disconnectednotify your server when a connection is made and when it ends. - The game loop. A game can show the community's quests, shop, locker, rank and daily reward for the connected player, and sell from its shop with the player's coins. See The game loop.
What follows
These are not in this release. Nothing on this page depends on them.
- SDKs for Unity and Unreal Engine are preview releases, currently in testing. Browser games use the Connect client; other engines use a game kit or the recipe.
Related
- Start connect, Get the result and Confirm connect
- Renew a player session, Get the player's profile and Disconnect
- Create a connect pass and Create a connect session
- Player data, The field map and Game rewards
- The game loop: quests, shop, locker, rank and the daily reward in the game, and the rules for spending
- Errors lists every code of Connect
- The TypeScript SDK has a Connect client for the game and functions for your own server.
Deliver shop items from your own platform
How a shop item that is Delivered by your API works: the signed request, the responses your server can return, the callback, refunds and retries.
Connect for any engine
Link a player of a game built with any engine to their Discord account in a community, with seven web requests and nothing else.

