twetch / connect
TWETCH CONNECT · INTEGRATION GUIDE

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.

Use the Connect service URL provided by OGKeK9 in the snippets below. The live service URL is https://ogkek9-connect.ogkek9.workers.dev. You only add the button and handle the player in your game.

Quick start

  1. Get the Connect service URL from OGKeK9.
  2. Add the script and a button to your game page.
  3. Implement POST /auth/twetch on your game server to exchange the returned code and create your own player session.
  4. Use the verified numeric Twetch ID to load inventory and apply your game’s rules.
HTML · your game page
<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.

Server handler outline
// 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

FieldTypeUse
data-twetch-connectHTML attributeAutomatically wires a button present when the script loads.
data-exchange-urlsame-origin pathYour server’s login handler, such as /auth/twetch.
TwetchConnect.mount(button, options)functionWire dynamically added buttons. Options: exchangeUrl, theme, onConnect, onError.
TwetchConnect.connect(options)PromiseCall 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-connectedCustomEventevent.detail contains your login handler’s response, or the raw grant when no exchangeUrl is provided.
twetch-errorCustomEventevent.detail.message describes cancellation, blocked popup or login failure.
Custom button
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.

POST /api/connect/exchange · response
{
  "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"
}
FieldTypeUse
player.twetchUserIdintegerStable identity key for linking your game account.
player.displayNamestringDisplay name captured at verification; it can change.
player.avatarUrlsstring[]Ordered image candidates; may be empty. Use a fallback avatar.
verification.methodstringCurrently twetch_bio_code. Proves ability to edit the profile.
verification.verifiedAtnumberUnix timestamp in milliseconds when the bio was checked.
verification.expiresAtnumberUnix timestamp in milliseconds when the Connect session expires.
audiencestringExact 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:

Game server inventory lookup
// 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.

Normalized inventory response
{
  "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
}
FieldTypeUse
items[].idstringInscription ID when available; otherwise collectionId:number. unidentified:index is display-only and must not grant item access.
items[].collectionstringDisplay collection name.
items[].collectionIdstringCollection identifier; use this for game eligibility rules.
items[].namestringDisplay item name.
items[].imageUrlsstring[]Ordered artwork URLs; may be empty.
items[].numbernumber | nullItem number when the index supplies one.
items[].raritystring | nullRarity label when available.
items[].traitsTrait[]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[].traitsStatusloaded | not_loadedOnly loaded + an empty traits array means no published traits.
fetchedAtnumberUnix 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:

Game server · read published traits
// 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.

Styled button
<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

EndpointAuthenticationResult
POST /api/connect/exchangeOne-time code + proof verifierPlayer, verification and audience. Send JSON with code, codeVerifier and exact origin. No API key.
GET /api/connect/playerConnect’s session cookiePlayer + verification. Same service origin; not a cross-domain game API.
GET /api/connect/collectionConnect’s session cookieNormalized items + fetchedAt. Traits may need a detail lookup.
GET /api/connect/item?collection=ID&number=NConnect’s session cookieItem 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 / symptomWhat to do
400Check JSON, code/verifier format and the exact origin. Origins have no path or trailing slash.
401 on exchangeCode 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.
413Send a JSON object smaller than 8 KiB.
403Check the request Origin and your configured game origin.
410 / 422 during verificationGenerate a new bio code if expired, or save the exact line and check again.
429Slow down verification checks or login attempts; rapid requests are rate limited.
503 / upstream failureShow a retry action. Keep login and inventory errors separate.
Popup blocked / closedCall connect during a click, allow popups, and show the SDK’s error message.
Popup cannot returnA strict Cross-Origin-Opener-Policy may sever window.opener. Check your page headers.
Script / images blockedAllow 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.