Connect your game to weBump
Games define their own private JSON saves, reviewed public fields and optional selected shared data. These steps apply to the real API. The separate API reference offers fictional requests for exploring the contract.
Prefer a working example?
Follow the seven-part Racing Demo tutorial to run a Godot game, register your own project, wire up Connect, save progress and share selected data. Each lesson includes source links and a checkpoint.
1. Apply for a project
Verify your email in the console and provide your game name,
description, icon, HTTPS launch URL, privacy and support pages, exact
HTTPS callback, requested permissions and public field definitions. Your icon
is screened automatically for harmful content the moment you submit it; a
rejected icon is never stored, and you can submit different artwork right
away. The callback must have no query string, fragment or custom port. After
submitting, publish the supplied TXT record at
_webump.<callback-domain> and click Verify domain.
DNS verification is required for approval. If review requests changes,
the note appears in your console; edit and resubmit there. Approval
provisions a public OAuth client ID. No shared game API key is needed.
2. Choose a connection method
A browser, game export or native app is downloadable. Your API URL and client ID are public configuration. Never bundle installation secrets, server keys or player tokens. A developer backend is optional.
Same-device callback approval: request
authorization_code and use PKCE. The player opens weBump,
approves, and returns through your registered HTTPS callback. There is
no matching-code step. Use this when the game and weBump run on the same phone.
Cross-device approval: request device_code
when the game runs on a computer, console or another device. The player
scans a QR code with their weBump phone, compares the short code and
approves. This also supports local development. It does not need a
developer backend or a new player login. Both methods require review.
What protects player data? Can someone copy my client ID?
An approved project plus the player's approval grants access. A client ID and API URL are public configuration, safe to include in a repository. Copying them can start a connection request; it cannot read protected player data. Approval in the weBump app authenticates the player through that installation's device-held credential and issues a scoped, expiring token to the authorized connection.
The callback controls where an authorization code is delivered. It does not require later API requests to come from that URL. Those requests use a bearer token and are checked for project, player, scope, expiry and revocation. CORS and DNS verification do not authenticate every running copy of a game.
Device approval intentionally authorizes the device the player confirms. Someone can run a copy locally and connect their own weBump profile. That does not give them another player's data. A malicious copy can still ask a player to approve access under a real project's name, so players must initiate Connect in a game they trust and compare the code on that game's screen with weBump before approving. The code helps bind that approval to the intended request; it does not certify the app.
Keep player tokens and private device codes out of source files, URLs,
logs and QR codes. Anyone who obtains a valid bearer token can use its
permissions until it expires or is revoked. Public-client OAuth is not
proof of an official app binary or an anti-cheat system. Anonymous
connection requests and authorized traffic have separate rate limits
and quotas; callback restrictions do not prevent request flooding.
Each client also gets a fair share of in-flight requests, and failed
or malformed calls use up that share faster than successful ones. On
429, wait for Retry-After and avoid
sending many parallel requests from one player.
Background: public clients and device-flow limits.
3. Ask the player to connect
For the usual same-phone experience, use callback approval: Connect → open weBump → approve permissions → return. The player does not compare or type a code. The SDK generates PKCE and state automatically. Native apps receive the registered HTTPS callback through their verified app link; websites receive it on their own origin.
A callback opened on a different phone or in a different browser cannot automatically reach the original browser tab. Use the reviewed device method for that situation; it authorizes the device the player confirms instead of delivering a code to the registered website. Never replace callback delivery with an endpoint that returns the code to whoever started a request.
Keep browser tokens in memory and native tokens in protected OS storage. If you need persistent browser sessions, a backend can keep tokens encrypted server-side behind Secure, HttpOnly cookies and origin/CSRF checks. Serialize refreshes and never retry a consumed refresh token. Any public game still needs the player's consent before accessing their real data.
Browser quickstart: one connection call
Self-host webump.js and request both
connection methods during project review. Use this on the same HTTPS
origin as your callback. On an iPhone/iPad, auto uses callback
approval without a matching code. On a computer, it uses phone approval.
Your game handles its UI and keeps the returned tokens in memory; the
helper handles PKCE, callback listening, device polling and cancellation.
import { connectBrowser } from './webump.js';
// Inside your Connect button's click handler:
const controller = new AbortController();
const tokens = await connectBrowser({
clientID: 'YOUR_PUBLIC_CLIENT_ID',
callbackURL: 'https://your-game.example/callback',
scopes: ['profile.basic', 'game.state'],
signal: controller.signal,
onPrompt(prompt) {
// Render a link to prompt.appURL || prompt.authorizationURL.
// For device_code, display prompt.userCode and prompt.qrURL first.
// For authorization_code, open prompt.appURL; retain a tappable fallback.
}
});
// Catch connection failures and offer a fresh Connect attempt.
// Cancel with controller.abort(); never log tokens.
For the callback page, use the ready-to-host return prompt, or call relayRedirectCallback({callbackURL})
as shown below. Keep the game tab open and use the same browser for
approval. The generic return prompt never opens or loads the game; it simply says “Go back to your game.” The browser callback still needs to open to deliver approval. A failed callback never silently switches to device approval.
Set mode: 'redirect' or mode: 'device' when your
integration explicitly chooses one reviewed method.
Embedding an export on a different origin? The same module provides
attachGameFrame({frame, frameURL, clientID, callbackURL, scopes, onPrompt}).
Install it on the callback host before assigning the iframe's URL.
It pins the game URL, validates the sender's origin and frame, owns the
connection, and sends the result only to that game. The
demo export shell
shows the small Godot bridge. No developer backend is required.
Optional backend sessions
Keep the long-lived session cookie Secure, HttpOnly and SameSite=Strict. To bind the cross-site OAuth return to the initiating browser, use a separate short-lived, Secure, HttpOnly, SameSite=Lax transaction cookie scoped to your host, plus server-side state and PKCE. Lax permits the top-level GET callback; Strict may omit the cookie on that return. Consume the transaction once, rotate the session after success and redirect to a clean URL. Do not rely on state alone without binding it to the browser. Keep callback pages free of analytics and third-party resources, use Cache-Control: no-store and Referrer-Policy: no-referrer, and redact codes from access logs.
With a backend session, a popup must be opened from the player’s click. Browser and app handoff behavior varies, so retain a full-page fallback and save locally before leaving. If using postMessage, verify both event.origin and event.source against the exact callback origin and popup window; send only a completion signal, never tokens or authorization codes. Recheck connection status with your backend. There is no official web session SDK or shared demo proxy supplied by this platform.
4. Save game-defined JSON
{"inventory":[{"item":"seed","quantity":3}],
"world":{"region":"meadow","unlocked":[1,2,5]},
"settings":{"sound":true}}
GET /v1/me/state returns your private data and an ETag. PUT /v1/me/state/progress with {"value":...} and If-Match writes a named key. State allows up to 16 keys and 16 KiB total, including nested objects, arrays (up to 256 items each), strings (up to 1,024 characters each), numbers, booleans and null. It is not a fixed high-score schema. Keep the primary save locally; this storage supplements it.
The revision is shared across state, capsule, showcase and shared-data writes. Preserve it in your client’s write queue or optional backend. On 409, fetch current data and merge deliberately without discarding the local draft. Retry safe reads with bounded backoff and jitter; respect Retry-After. Do not blindly retry writes or refresh tokens.
5. Show a public game card
Request game.showcase and declare up to five reviewed fields. Fields may be bounded integers, booleans or reviewed enum values, with developer-defined labels:
{"capsule":{},"showcase":{
"flowers":{"label":"Flowers grown","type":"integer","min":0,"max":1000000},
"season":{"label":"Season","type":"enum","values":["spring","summer"]}}}
PUT /v1/me/showcase with {"value":{...}} and the current If-Match. The player decides whether to enable Show on my profile. Everyone viewing that profile can then see the approved game icon, title, selected fields and launch link. Private saves remain private. New reviewed games require no iOS update.
Upload a square static PNG, 128–512 pixels, RGB/RGBA, at most 200 KB. Assets, labels and links are reviewed with the application. Icons are served by weBump rather than developer tracking URLs.
Before adding refresh loops or autosaves, read the rate-limit budgets and retry guide. Project limits are shared by all your players.
6. Receive visitors
With visitors.receive, GET /v1/me/visitors lists the people your player bumped, including verifiable recent history synced by weBump, once the bump is unwrapped and the visitor's chosen profile delay has passed and while they still allow sharing. Each visitor carries a reference, display name, color and, when the peer shares them, reviewed game stats. Refresh the list when your game gains focus or before a session, with bounded polling and Retry-After backoff within your request budget; listing marks the visitors delivered. Nothing else is needed: no second approval, no button. POST /v1/me/visitor-handoff remains available when you want the player to hand over a specific set on demand.
References are temporary and project-scoped. Recheck cards through GET /v1/me/visitors/{reference}; expired, delayed, blocked or withdrawn cards are unavailable. Keep reward deduplication alongside the local save and commit the reward and processed reference atomically. Remove cached personal data when access is withdrawn. The API reference's synthetic action=simulate is not a production operation.
Cards also include visitor_id: an opaque ID for recognizing the same visitor within this game and receiving player. Different recipients and games receive different IDs, and name/color changes do not affect it. Group repeat cards by that field, keeping a valid reference for API reads. The ID itself cannot fetch anything. Never infer identity from name and color; older cards without the field can only be deduplicated by exact reference.
Recognition continues while unexpired seven-day receipts link the pair. Polling does not extend it. An expiry gap, visitor opt-out or game exclusion, block, explicit game disconnect/revocation or deletion resets the affected relationship. Unblocking does not restore old references. A new installation is a new identity.
Connection methods, SDK examples and Godot exports
Use the Compatibility renderer and a single-threaded Web export as a starting point. Place the HTML connect control in your custom export shell outside the canvas. The downloadable game never contains server credentials.
Two explicit connection methods, any platform
Request the methods your integration needs in the application or project change request: authorization_code (registered callback + PKCE), device_code (phone approval), or both. Existing projects start redirect-only until device access is reviewed. A public client ID identifies a project; it is not a secret or proof that downloadable software is genuine.
Redirect connections: use the registered HTTPS callback and PKCE. Open the authorization URL, then handle the callback through your website or app link. On approval, weBump always opens that exact callback. /oauth/pending reports status only, for GET, POST and peek alike. It cannot return a callback, code or token; display=popup changes presentation only. Custom postMessage relays must validate both message origin and source, exact callback and state. The helper below uses an origin-scoped BroadcastChannel instead. Browser broadcasts cannot move approval between devices.
Device connections: POST /oauth/device_authorization with {"client_id":"YOUR_ID","scope":"profile.basic game.state"}. No callback or PKCE parameters belong in this request. Display the returned user_code prominently beside qr_url and a link to verification_uri. On the same iPhone, show the code before opening app_url. The player must match the code and explicitly approve the device in the updated weBump app. Manual entry is available at webump.app/connect.
Keep device_code private in memory; never put it in a QR, URL, log, screenshot, repository or browser storage. After the returned interval (at least five seconds), POST /oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code, client_id and device_code. Retry authorization_pending at that interval; increase the interval by five seconds on each slow_down. Stop on denial, expiry or other errors. A successful response returns the normal scoped tokens exactly once. If that response is lost, start a fresh connection rather than replay redemption.
This works for websites, mobile/desktop apps, consoles and local development. Device access deliberately does not enforce callback-domain delivery or prove a copied app is genuine; it authorizes the device the player confirms. Projects requiring callback delivery should remain redirect-only. A backend is optional for public clients; use protected server sessions when you need server-held credentials or sessions that survive reloads. CORS is not client authentication.
Browser callback approval without a developer backend
Self-host webump.js on the same HTTPS origin as your registered callback. Keep the initiating game tab open. The helper can receive the callback from a new tab in that browser without exposing it to a localhost copy or putting credentials in browser storage.
import { beginRedirectConnection } from './webump.js';
connectButton.onclick = async () => {
const approval = window.open('about:blank', 'webump-approval');
if (!approval) return; // Show a clear popup-blocked message.
try {
const connection = await beginRedirectConnection({
clientID: 'YOUR_PUBLIC_CLIENT_ID',
callbackURL: 'https://your-game.example/callback'
});
const result = connection.awaitTokens();
approval.location.href = connection.authorizationURL;
const tokens = await result; // Keep in memory.
// Continue using the scoped API.
} catch (error) {
approval.close();
// Show a connection error and offer a fresh Connect attempt.
}
};
On the registered callback page, run this from a self-hosted module.
Keep that page free of third-party scripts and send Cache-Control:
no-store and Referrer-Policy: no-referrer.
import { relayRedirectCallback } from './webump.js';
const result = relayRedirectCallback({
callbackURL: 'https://your-game.example/callback'
});
if (result) {
const received = await result;
// received means the waiting tab received the callback, not that token exchange succeeded.
// Show "Return to your game tab", or ask to reconnect if it was closed.
}
The relay uses an origin-scoped browser channel plus the initiating request's state, and exchanges the code with PKCE. It never polls for credentials. Browser storage separation means approval must return to the same browser on the same device. Embedded games additionally need an explicit host/frame bridge that validates both message origin and source.
JavaScript: copy the helper, configure your project
Download the dependency-free ES module. Self-host it. The helper keeps the polling credential private and returns tokens for you to keep in memory. Cancel with an AbortSignal. It never writes localStorage or handles refresh automatically.
import { beginDeviceConnection } from './webump.js';
const connection = await beginDeviceConnection({
clientID: 'YOUR_PUBLIC_CLIENT_ID', scopes: ['profile.basic', 'game.state']
});
codeLabel.textContent = connection.userCode;
qrImage.src = connection.qrURL;
// Display verificationURL as a manual-entry fallback.
// Or offer a user-tapped link to connection.appURL on this iPhone.
const tokens = await connection.awaitTokens({ signal: abortController.signal });
// Keep tokens in memory; use Authorization: Bearer with scoped API requests.
The same module exports beginRedirectConnection, which creates PKCE/state and verifies the returned callback. Retain the connection object for a popup and call complete(callbackURL) only after checking the relay message's origin and source. For full-page navigation, implement protected transaction persistence or use device connection to keep the game page open.
Swift: native device connection
let client = try WeBumpClient(clientID: "YOUR_PUBLIC_CLIENT_ID") let connection = try await client.beginDeviceConnection(scopes: ["profile.basic"]) // Display connection.userCode, QR and verificationURL, then: try await client.awaitApproval(connection) // Tokens are now in device-only Keychain. No callback completion call.
Redirect clients supply callbackURL, call beginConnection and complete using the app's registered callback. The former polling-based awaitApproval(ConnectionSession) is removed; it must not be used for redirect or legacy visitor-handoff requests. New clients use GET /v1/me/visitors for automatic eligible visitors. The hosted Godot demo uses this helper: callbacks on the same iPhone/iPad, device approval on computers. Direct exports without its host bridge retain device approval.
Serve WASM with application/wasm. A restrictive game-page CSP may need
script-src 'self' 'wasm-unsafe-eval' for WASM compilation
and a nonce or hash for the export shell’s bootstrap script. Adjust
worker-src and connect-src only for resources your build actually
uses. Test the exported build in Safari and Chrome. Apply
game-specific headers only to the game routes. Threaded exports
require cross-origin isolation (COOP/COEP), which affects popup
communication and external assets; do not turn it on across an
unrelated website.
Private game.state holds only the connected player's save. Use game.shared for a separately selected, reviewed JSON document that authorized visitors may retrieve. Keep larger data in your own storage under your own consent controls. Real visitors remain subject to the visitor’s chosen profile delay and privacy checks. Use the console’s fictional API playground for immediate contract exploration; its simulate operation is unavailable in the real API.
7. Share selected game data
Use game.shared for a custom design, challenge, recording or other game-defined data. It is a separate selected copy, never a read of someone else's private save. It is delivered only through authorized visitor references and never appears on weBump profile cards.
- Request
game.sharedand declaredata_definition.sharedin your application. Addvisitors.receiveto retrieve visitor data. Scope and schema changes require review; an existing approval does not gain new permissions automatically. - Request the reviewed scopes when connecting. The player separately enables “Share selected game data with visitors” in weBump. This starts off, including for existing players. The game cannot enable it through its API token. Check
GET /v1/me/permissionsforshared_data_sharing. - Explain which data your game selects to share, such as automatically sharing new best replays while the player’s weBump sharing option is enabled. GET
/v1/me/shared, then PUT that path with the returned strongIf-MatchETag and{"publish":true,"value":{...}}. Send only the selected data. There is one published document per player per project, up to 16 KiB in both JSON wire and PostgreSQL JSONB text representations. A missingpublishflag returns400 publish_required; a player who has not enabled sharing returns403 consent_required, so show them where the weBump toggle is rather than retrying. - After redeeming a visitor handoff, GET
/v1/me/visitors/{reference}/sharedwith the receiving player's token. The result is{"reference":"…","data":{...},"expires_at":"…"}. No private save, global identity or encounter timestamp is included.
Example: a developer-defined design schema
Include this alongside any capsule/showcase definitions. The field names belong to your game; the API does not prescribe scores, tracks, recordings or designs.
{"capsule":{},"showcase":{},"shared":{
"type":"object","additionalProperties":false,
"required":["version","tiles"],
"properties":{
"version":{"type":"integer","minimum":1,"maximum":1},
"tiles":{"type":"array","maxItems":64,"items":{
"type":"object","additionalProperties":false,
"required":["x","y","kind"],"properties":{
"x":{"type":"integer","minimum":0,"maximum":7},
"y":{"type":"integer","minimum":0,"maximum":7},
"kind":{"type":"string","maxLength":8,"enum":["grass","water"]}
}
}}
}
}}
A matching publication body:
{"publish":true,"value":{"version":1,"tiles":[{"x":2,"y":3,"kind":"grass"}]}}
Supported schema and size limits
This is a bounded subset of JSON Schema. The root must be an object. Every object declares properties and additionalProperties:false; optional required lists declared keys. Each object may declare up to 64 keys, using 1–64 ASCII letters, digits, underscores or hyphens. Reserved prototype keys are rejected.
Arrays declare items and maxItems (1–256); optional minItems bounds the minimum. Fixed tuples use prefixItems (1–16 schemas), items:false, and equal minItems/maxItems matching the tuple length. Nested arrays are supported.
Strings require maxLength (1–1,024 Unicode characters, also at most 4,096 UTF-8 bytes); control characters are rejected. Numbers and integers require finite minimum/maximum within ±1 trillion. Booleans and null are supported. Primitive types may have an enum of 1–32 valid values. Schemas allow at most 128 nodes and depth 8 (root depth 0).
Other keywords, including remote references, patterns and unions, are rejected. Undeclared data fields are rejected. Keep payloads below the total 16 KiB limit with room for JSONB spacing; a request body is capped at 20,000 bytes. Treat received values as untrusted data: validate your game rules and never evaluate code, render raw HTML, or fetch URLs supplied inside them. Use reviewed fields only for game data, never identifiers, contact details or tracking.
Eligibility, expiry and withdrawal
Both players need a current grant for the same game. The source player's connection, sharing choice and start of uninterrupted publication must predate the bump's rolling epoch. The first publication reaches later eligible bumps. Updating an unexpired publication preserves access for those eligible visitors; withdrawal or an expiry gap requires a new eligible bump. This first version retains one current publication, not a version history.
Publications expire seven days after each explicit publication. A visitor's access also ends when their reference expires, whichever comes first. Existing reveal delays, basic visitor sharing, per-game exclusions, blocks, suspended projects and disconnected players still apply. Opting back in does not restore an erased copy or revive older references.
DELETE /v1/me/shared with the current If-Match removes the published copy and keeps private saves. Disabling shared-data consent, disconnecting, grant revocation, or ownership transfer also erases it. Expired data is inaccessible immediately and removed by normal maintenance. Recheck before use and remove cached copies on 410 consent_required or expiry; downloaded copies cannot be recalled. A network failure is not a deletion signal. Handle unavailable data with your game's normal fallback.
The sharing control requires the updated weBump app. Older clients cannot silently opt players in. In the Swift SDK use sharedData(), publishSharedData(_:revision:), deleteSharedData(revision:) and visitorSharedData(reference:). The fictional playground currently demonstrates the older card/state operations; test shared-data consent and handoffs on the isolated development stack.
8. Disconnect and release
POST /oauth/revoke with client_id and the player's token to revoke access. A player can also disconnect or delete game data in weBump. Test consent cancellation, invalid state, code replay, concurrent refresh, save conflicts, disconnect, hiding public cards and withdrawn visitors on real devices before release.
Interactive API reference · Connect button guidelines · Exports and departure · Swift SDK
