Your players.
Their Twetch identity.
Embed one button, verify the login on your server, and use the player’s public profile and collection in your game.
Quick start
- Get the Connect service URL from OGKeK9.
- Add the script and a button to your game page.
- Implement
POST /auth/twetchon your game server to exchange the returned code and create your own player session. - Use the verified numeric Twetch ID to load inventory and apply your game’s rules.
<button data-twetch-connect data-exchange-url="/auth/twetch">
Connect with Twetch
</button>
<script src="https://ogkek9-connect.ogkek9.workers.dev/twetch-connect.js" defer></script>
<script>
document.querySelector('[data-twetch-connect]')
.addEventListener('twetch-connected', event => {
// This is the response from YOUR /auth/twetch server handler.
console.log(event.detail.player);
});
</script>The script opens a compact popup. Verified players return automatically; new players add a temporary bio line and verify first. Style the button with your own CSS. Render user names as text, and try avatar URLs in order if one fails.
// In your game's server login handler:
const CONNECT_ORIGIN = 'https://ogkek9-connect.ogkek9.workers.dev';
// Set GAME_ORIGIN to your game’s exact HTTPS origin in server configuration.
if (request.headers.get('origin') !== GAME_ORIGIN) {
return new Response('Invalid origin', { status: 403 });
}
const { code, codeVerifier } = await request.json();
const response = await fetch(CONNECT_ORIGIN + '/api/connect/exchange', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ code, codeVerifier, origin: GAME_ORIGIN })
});
const verified = await response.json();
if (!response.ok) return Response.json(verified, { status: response.status });
if (verified.audience !== GAME_ORIGIN ||
verified.verification.expiresAt <= Date.now()) {
return new Response('Invalid player', { status: 401 });
}
// Create your own server session keyed by verified.player.twetchUserId.
// Use your session library to set an HttpOnly, Secure cookie.
// Then return { player: verified.player } to your button.
// Return { player: verified.player } after creating your game session.Browser SDK
| Field | Type | Use |
|---|---|---|
data-twetch-connect | HTML attribute | Automatically wires a button present when the script loads. |
data-exchange-url | same-origin path | Your server’s login handler, such as /auth/twetch. |
TwetchConnect.mount(button, options) | function | Wire dynamically added buttons. Options: exchangeUrl, theme, onConnect, onError. |
TwetchConnect.connect(options) | Promise | Call directly during a user click. Resolves to code, codeVerifier, origin and connectOrigin when no exchangeUrl is set. With exchangeUrl, resolves to your server’s JSON response. |
twetch-connected | CustomEvent | event.detail contains your login handler’s response, or the raw grant when no exchangeUrl is provided. |
twetch-error | CustomEvent | event.detail.message describes cancellation, blocked popup or login failure. |
TwetchConnect.mount(document.querySelector('#play'), {
exchangeUrl: '/auth/twetch',
onConnect: data => showPlayer(data.player),
onError: error => showError(error.message)
});connectOrigin is an optional SDK override. Your server must use its own configured service URL, rather than trusting a URL received from the browser. Load the script from a trusted Connect host; it runs with your page’s permissions.
Player data
The exchange endpoint returns this shape. Your own login handler may return fewer fields to the browser.
{
"player": {
"twetchUserId": 742,
"displayName": "Player name",
"avatarUrls": ["https://media.ordinalswallet.com/avatar.gif"]
},
"verification": {
"method": "twetch_bio_code",
"verifiedAt": 1791580000000,
"expiresAt": 1792184800000
},
"audience": "https://your-game.example"
}| Field | Type | Use |
|---|---|---|
player.twetchUserId | integer | Stable identity key for linking your game account. |
player.displayName | string | Display name captured at verification; it can change. |
player.avatarUrls | string[] | Ordered image candidates; may be empty. Use a fallback avatar. |
verification.method | string | Currently twetch_bio_code. Proves ability to edit the profile. |
verification.verifiedAt | number | Unix timestamp in milliseconds when the bio was checked. |
verification.expiresAt | number | Unix timestamp in milliseconds when the Connect session expires. |
audience | string | Exact game origin. Match against your server configuration. |
Profile data is a verification snapshot. This connection does not prove possession of a wallet private key or authorize transfers.
Inventory data
The Connect preview loads GET /api/connect/collection with its verified session cookie. A game on another domain cannot reuse that cookie. For a game on another domain, read the public inventory using the verified player ID on your game server:
// Derive this ID from your authenticated server session.
const userId = session.player.twetchUserId;
const response = await fetch(
'https://api.twetch.com/v1/wallet/' + userId + '/tokens'
);
if (!response.ok) throw new Error('Inventory temporarily unavailable');
const result = await response.json();
if (!Array.isArray(result.data)) throw new Error('Unexpected inventory');
// Twetch returns the indexed tokens in result.data.
// Use these records for your game’s inventory view.
return { tokens: result.data, fetchedAt: Date.now() };The direct Twetch lookup returns its own token records. The normalized response below describes the collection data shown by the Connect service. Its cookie-based collection endpoint works on the Connect origin; a cross-domain inventory API is not currently provided.
{
"items": [{
"id": "inscription-id",
"collection": "Fox Syndicate",
"collectionId": "collection-transaction-id",
"name": "Fox Syndicate #235",
"imageUrls": ["https://media.ordinalswallet.com/item.png"],
"number": 235,
"rarity": "Uncommon",
"traits": [],
"traitsStatus": "not_loaded"
}],
"fetchedAt": 1791580000000
}| Field | Type | Use |
|---|---|---|
items[].id | string | Inscription ID when available; otherwise collectionId:number. unidentified:index is display-only and must not grant item access. |
items[].collection | string | Display collection name. |
items[].collectionId | string | Collection identifier; use this for game eligibility rules. |
items[].name | string | Display item name. |
items[].imageUrls | string[] | Ordered artwork URLs; may be empty. |
items[].number | number | null | Item number when the index supplies one. |
items[].rarity | string | null | Rarity label when available. |
items[].traits | Trait[] | Published traits: type, value (string, number or boolean), rarity (string or null), count (number or null). Count is the published number of items sharing the trait. |
items[].traitsStatus | loaded | not_loaded | Only loaded + an empty traits array means no published traits. |
fetchedAt | number | Unix timestamp in milliseconds for this inventory fetch. |
Large inventories
Connect preserves every item returned by Twetch’s index, with no app-level total-item cap. The preview searches the full inventory and browses 48 cards per page; large game character pickers browse 100 choices per page. These display windows do not truncate the API response. Use paging or virtualization in your game, and retain all returned items. Missing upstream records cannot be recovered by the display.
See traits affect gameplay
The Star Run demo turns published trait rarity into game abilities: Rare or higher gives a crystal magnet, Epic or higher gives double points, and Legendary or Exotic gives two rock shields. Each ability displays its source trait. These are demo rules, not Twetch-provided gameplay stats. The abilities toggle provides a comparison with the basic game. Your game can assign its own effects to trait types, values or rarity; check indexed ownership and metadata on your server before granting them.
Read an item’s traits
The wallet index usually omits traits. On the Connect origin, request GET /api/connect/item?collection=COLLECTION_ID&number=235 with the verified session cookie. It checks the item is still in the indexed wallet and returns { item, fetchedAt } with traitsStatus: "loaded". The demo loads this when you expand an item. Retry failed requests; do not treat them as missing traits.
For a game on another domain, select the item from your server’s wallet lookup above, then read its public details:
// ownedItem comes from this player's latest wallet lookup.
const response = await fetch('https://api.twetch.com/v1/tokens/' +
encodeURIComponent(ownedItem.collection) + '/' + ownedItem.token.number);
if (!response.ok) throw new Error('Traits temporarily unavailable');
const detail = await response.json();
if (!detail.token) throw new Error('Unexpected item details');
const attributes = detail.token.attributes ?? [];
// Twetch attributes: { traitType, value, rarity?, count? }
// Example: { traitType: 'Mouth', value: 'Gold Teeth', rarity: 'Epic', count: 39 }
// Use exact traitType + value for your game's rules; do not invent stats.Use collection IDs and durable item IDs for rules. Fetch inventory again on your server before granting an item-based benefit. Refresh on every game login or session restore, on return to the game tab, and before starting gameplay or granting an item benefit. Replace the previous inventory snapshot; remove sold items from available choices. If refresh fails, pause item-based access and offer retry. The demo rescans on return and before every round. An empty list is different from a failed lookup: report failures and allow retry. Public indexed ownership is a snapshot and may lag transfers; it is not a wallet signature.
32 button colors
Choose violet, mint, blue, coral, rose, amber, white, black, emerald, forest, teal, cyan, sky, navy, indigo, lavender, plum, grape, magenta, pink, blush, red, crimson, orange, peach, gold, lemon, lime, olive, sand, slate or silver. The optional stylesheet supplies the colors, hover, focus and disabled states. You can also use your own CSS.
<link rel="stylesheet" href="https://ogkek9-connect.ogkek9.workers.dev/twetch-connect.css">
<button class="twetch-connect-button" data-theme="mint"
data-twetch-connect data-exchange-url="/auth/twetch">
Connect with Twetch
</button>
<script src="https://ogkek9-connect.ogkek9.workers.dev/twetch-connect.js" defer></script>For dynamic buttons, pass theme: 'mint' to TwetchConnect.mount and include the stylesheet. Choose a palette in the live playground to copy the exact embed code.
API reference
| Endpoint | Authentication | Result |
|---|---|---|
POST /api/connect/exchange | One-time code + proof verifier | Player, verification and audience. Send JSON with code, codeVerifier and exact origin. No API key. |
GET /api/connect/player | Connect’s session cookie | Player + verification. Same service origin; not a cross-domain game API. |
GET /api/connect/collection | Connect’s session cookie | Normalized items + fetchedAt. Traits may need a detail lookup. |
GET /api/connect/item?collection=ID&number=N | Connect’s session cookie | Item with loaded traits + fetchedAt; indexed ownership required. |
The SDK manages the player connection for you. Responses are uncached. Exchange supports matching browser-origin CORS; server exchange is recommended so the game can issue its own session.
Sessions and disconnect
Your game owns its login session. Implement a same-origin logout handler that deletes your session and clears your game’s cookie, then remove the player and inventory from the UI. Use your game’s existing logout handler for this. Logging out of a game does not revoke the Connect verification session, so returning players can sign in again without editing their bio.
The Connect preview’s Disconnect button also revokes the Connect verification session. There is currently no SDK-wide logout or remote game-session revocation endpoint. Session length is your game’s choice; cap it by the verification expiry. Connect’s seven-day session is not a seven-day game session.
Errors and troubleshooting
| Response / symptom | What to do |
|---|---|
| 400 | Check JSON, code/verifier format and the exact origin. Origins have no path or trailing slash. |
| 401 on exchange | Code expired, was used, its verification session was revoked, proof did not match, or game origin was wrong. Start a new connect flow; do not retry the consumed code. |
| 413 | Send a JSON object smaller than 8 KiB. |
| 403 | Check the request Origin and your configured game origin. |
| 410 / 422 during verification | Generate a new bio code if expired, or save the exact line and check again. |
| 429 | Slow down verification checks or login attempts; rapid requests are rate limited. |
| 503 / upstream failure | Show a retry action. Keep login and inventory errors separate. |
| Popup blocked / closed | Call connect during a click, allow popups, and show the SDK’s error message. |
| Popup cannot return | A strict Cross-Origin-Opener-Policy may sever window.opener. Check your page headers. |
| Script / images blocked | Allow the trusted Connect script host in your CSP and approved artwork hosts in img-src. |
Do not log authorization codes, proof verifiers, cookies or wallet secrets. Never forward browser-provided URLs to your server fetch. This project is an independent integration, not an official Twetch wallet SDK.