logo
Development
Search
Quick Start

Quick Start

Get a complete "identity handoff" working end to end with the least amount of code — when a workspace user opens your organization's extension app, their identity information is passed along so your extension app's backend can correctly identify the current user and retrieve their identity details.

The quick tutorial below uses the Push mode for a simple demonstration:

Prerequisite: Obtain the Secret

You first need to obtain the extension app's HS256 signing secret, which is used to complete the organization extension app's identity handoff.

  • Have the workspace OWNER/ADMIN go to Workspace → Space Management → Extension Apps and click "Add"
  • App name, app icon, App entry URL (app_home_url)
  • For the auth mode, choose workspace_account (identity passing required)
  • After submitting, the system will display the App Secret in plaintext one time only — copy and save it immediately. Once the dialog is closed, it can never be viewed again and can only be rotated.

The secret looks like wext_ + 64 hexadecimal characters (Tier 2). Store it only in your backend (environment variables / a secrets management service) — never put it in the frontend, git, or logs.

Step 1: Backend — The Token Verification Endpoint

Create a backend endpoint that receives the wsa POSTed from the frontend, verifies it with the secret, and, on success, establishes your own session.

// Node / Express example import express from 'express'; import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; const app = express(); app.use(express.json()); app.post('/session/exchange', (req, res) => { try { const identity = verifyWsa(req.body.wsa, { secret: process.env.EXTENSION_APP_SECRET, // your secret (backend only) audience: 'app.example.com', // your app host, must equal the token's aud }); // identity = { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? } const sid = createYourSession(identity); // replace with your own session res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' }); res.json(identity); } catch (e) { const code = e instanceof WsaVerificationError ? e.code : 'Error'; res.status(401).json({ code }); // reject on any verification failure } });
                      
                      // Node / Express example
import express from 'express';
import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

const app = express();
app.use(express.json());

app.post('/session/exchange', (req, res) => {
  try {
    const identity = verifyWsa(req.body.wsa, {
      secret: process.env.EXTENSION_APP_SECRET, // your secret (backend only)
      audience: 'app.example.com',              // your app host, must equal the token's aud
    });
    // identity = { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? }

    const sid = createYourSession(identity);    // replace with your own session
    res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' });
    res.json(identity);
  } catch (e) {
    const code = e instanceof WsaVerificationError ? e.code : 'Error';
    res.status(401).json({ code });             // reject on any verification failure
  }
});

                    
This code block in the floating window

verifyWsa verifies in order: signature → issaudexp (including iat/nbf) → required claims. Any failure throws a WsaVerificationError. See 06 - SDK API Reference for details.

Step 2: Frontend — Consume the Token on the Landing Page

The user is brought by the platform to your app_home_url?wsa=<JWT>. On this landing page, read the wsa, POST it to your own backend, and then strip it from the URL.

// your landing page import { consumeHandoff } from '@gptbots/workspace-extension-sdk'; const identity = await consumeHandoff({ exchangeUrl: '/session/exchange' }); // consumeHandoff does the following in sequence: // 1) reads ?wsa= from the current URL // 2) POSTs { wsa } to /session/exchange (your backend calls verifyWsa here) // 3) on success, strips ?wsa= from the URL via history.replaceState // 4) returns the identity your backend sent back console.log('current workspace user:', identity.username, identity.role); if (identity.role === 'MEMBER') hideAdminUI();
                      
                      // your landing page
import { consumeHandoff } from '@gptbots/workspace-extension-sdk';

const identity = await consumeHandoff({ exchangeUrl: '/session/exchange' });
// consumeHandoff does the following in sequence:
// 1) reads ?wsa= from the current URL
// 2) POSTs { wsa } to /session/exchange (your backend calls verifyWsa here)
// 3) on success, strips ?wsa= from the URL via history.replaceState
// 4) returns the identity your backend sent back

console.log('current workspace user:', identity.username, identity.role);
if (identity.role === 'MEMBER') hideAdminUI();

                    
This code block in the floating window

Just like that, these two snippets complete a secure identity handoff.

The Full Flow at a Glance

Workspace "Extensions" page GPTBots platform Your app ──────────────── ─────────── ──────── User clicks your app icon ───────────────▶ Verify the clicker is a member of this workspace Sign a wsa (JWT) valid for 5 minutes with the secret Open https://app.example.com/?wsa=<JWT> ─────────────────────────────▶ Landing page consumeHandoff() ◀── POST /session/exchange { wsa } ── Your backend verifyWsa() → identity Establish your own session Strip ?wsa= from the URL
                      
                      Workspace "Extensions" page                GPTBots platform          Your app
────────────────                           ───────────               ────────
User clicks your app icon ───────────────▶ Verify the clicker is a member of this workspace
                                           Sign a wsa (JWT) valid for 5 minutes with the secret
Open https://app.example.com/?wsa=<JWT> ─────────────────────────────▶ Landing page
                                                                     consumeHandoff()
                                          ◀── POST /session/exchange { wsa } ──
                                                                     Your backend verifyWsa() → identity
                                                                     Establish your own session
                                                                     Strip ?wsa= from the URL

                    
This code block in the floating window

Local Debugging Tips

  • The wsa is valid for only 5 minutes and is a one-time bootstrap token — after exchanging it for your own session, do not include wsa in subsequent requests.
  • audience must exactly equal the host you registered (app.example.com); port/protocol are not part of the aud, but the host must match, otherwise you get WrongAudience.
  • On a verification failure, first check WsaVerificationError.code (InvalidSignature / Expired / WrongAudience …) and cross-reference 07 - Troubleshooting.
  • Want to just read the identity for display without creating a session? Replace consumeHandoff with readHandoffToken() (see 02 - The Three Consumption Levels).
    Next step: read 02 - Core Concepts to understand the token contract and the two modes, or go straight to 03 - Push Handoff / 04 - Pull Login for the full details.
    Integrate your own web system into the GPTBots workspace as an "extension app." When a workspace user opens your app from Workspace → Extensions, GPTBots securely hands off the user's workspace identity to you as a short-lived signed token (wsa, a JWT). Your app uses this to identify the user without a separate login and unlock features by role.

Table of Contents

File Audience Contents
Quick Start.md Everyone Get a complete identity handoff working in 10 minutes (with minimal usable frontend + backend code)
Core Concepts.md Everyone The two extension-app layers, the two integration modes, auth_mode, the three consumption levels, and the wsa token contract
Push Handoff.md Push integrators User opens the app on the extensions page → the platform pushes the wsa to you (the ?wsa= landing page)
Pull Login.md Pull integrators Your app places a "Login with GPTBots Workspace" button (OAuth2 authorization code + PKCE)
Token Verification and Security.md Everyone (required reading) The mandatory verification checklist, secret custody, and multi-language verification without the SDK (Java / Node / Python)
SDK API Reference.md Everyone The complete API, types, and error codes for the two SDK packages
Troubleshooting.md Everyone FAQ, the master error-code table, and common pitfalls

SDK Overview

The official SDK is framework-agnostic with zero runtime dependencies and consists of two packages (both inside the archive workspace-extension-sdk-0.1.0.zip):

Package Runs in Purpose
@gptbots/workspace-extension-verify Your backend (Node) Verify wsa with the secret → get a WorkspaceIdentity; exchange codewsa on the backend
@gptbots/workspace-extension-sdk Browser Read / strip / exchange the wsa; initiate "Login with GPTBots Workspace"

You don't have to use the SDK — the wsa is a standard HS256 JWT, and any language's JWT library can verify it. See 05 - Token Verification and Security.

Installing Locally from the Archive

# After extracting, both packages ship with a prebuilt dist and can be used as local dependencies directly: unzip workspace-extension-sdk-0.1.0.zip npm i ./workspace-extension-sdk/packages/verify # backend npm i ./workspace-extension-sdk/packages/browser # frontend
                      
                      # After extracting, both packages ship with a prebuilt dist and can be used as local dependencies directly:
unzip workspace-extension-sdk-0.1.0.zip
npm i ./workspace-extension-sdk/packages/verify   # backend
npm i ./workspace-extension-sdk/packages/browser  # frontend

                    
This code block in the floating window

Once the SDK is published to npm, you can simply run npm i @gptbots/workspace-extension-verify / npm i @gptbots/workspace-extension-sdk.

Pre-Integration Alignment Checklist

  • Confirmed you have already created the organization extension app and obtained its secret
  • Decided on the integration mode: Push or Pull (M-Auth)
  • The app_home_url (push) / the host of redirect_uri (pull) exactly matches the registered information
  • The backend has implemented wsa verification: signature + iss + aud + exp — all four are mandatory
  • On landing, immediately history.replaceState to strip wsa / code from the URL
  • Exchanged the token for the app's own session; subsequent requests no longer pass through the wsa
  • The app server's clock is NTP-synced