For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Apply for AccessDashboard
Guides
Guides
  • Get Started
    • Introduction
    • Quickstart
  • Agent Identity
    • Overview
    • Create Auth Intent
    • Exchange for JWT
    • Sessions
    • JWKS
  • Agent Checkout
    • Overview
    • Create an Invoice
    • List Invoices
    • Get Invoice
    • Cancel Invoice
  • Integration Patterns
    • Overview
    • Shared SDK Client
    • Wallet Auth
    • JWT Verification
    • Subscription Checkout
    • Webhook Verification
  • Concepts
    • Two Invoice IDs
    • Wallet Identity
    • Session vs Token
    • Webhook-Driven State
  • Webhooks
    • Overview
    • Verify Signatures
  • Reliability
    • Errors
    • SDK Reference
Apply for AccessDashboard
On this page
  • Exchange for JWT
  • Parameters
  • Response
  • JWT sub format
  • Errors
  • Next step
Agent Identity

Exchange Auth Intent

Exchange a settled auth intent for a JWT
||View as Markdown|
Was this page helpful?
Edit this page
Previous

Create Auth Intent

Next

Sessions

Built with

Exchange for JWT

After the agent pays the auth challenge, exchange the intent for a JWT:

1import { Gwop } from "@gwop/sdk";
2
3const gwop = new Gwop({
4 merchantApiKey: process.env.GWOP_MERCHANT_API_KEY,
5});
6
7const { result: token } = await gwop.authIntents.exchange({
8 authIntentId: "ai_mn5e49p01TJe5YUJ-Cw",
9 idempotencyKey: crypto.randomUUID(),
10});
11
12console.log(token.accessToken); // RS256-signed JWT
13console.log(token.principal.sub); // "base:0x742d..." or "solana:7sSi..."
14console.log(token.account.isNewAccount); // true on first auth

Parameters

FieldTypeRequiredDescription
authIntentIdstringYesThe authIntentId from authIntents.create()
idempotencyKeystringNoUUID v4 for safe retries

Response

The response contains:

FieldDescription
accessTokenRS256-signed JWT for authenticating subsequent requests
principal.subWallet identity: {chain}:{address}
account.isNewAccounttrue if this is the wallet’s first authentication

JWT sub format

The sub claim identifies the wallet:

base:0x742d35Cc6634C0532925a3b844Bc9e7595f5bA16
solana:7sSi2XK9pJuqMV9p4Lz3kxBRtxYRPcC5Yp7CYGkaFqJ

Parse it to extract the chain and address:

1const [chain, address] = token.principal.sub.split(":");
2// chain = "base", address = "0x742d..."

Errors

1import { ErrorCode, isGwopError } from "@gwop/sdk/errors";
2
3try {
4 await gwop.authIntents.exchange({ authIntentId, idempotencyKey: crypto.randomUUID() });
5} catch (err) {
6 if (isGwopError(err, ErrorCode.AuthIntentNotSettled)) {
7 // Agent hasn't paid yet — poll with backoff
8 } else if (isGwopError(err, ErrorCode.AuthIntentNotFound)) {
9 // Invalid intent ID — create a new one
10 } else if (isGwopError(err, ErrorCode.AuthIntentExpired)) {
11 // TTL exceeded — create a new auth intent
12 } else if (isGwopError(err, ErrorCode.AuthIntentUsed)) {
13 // Already exchanged — use the JWT from the first exchange
14 } else {
15 throw err; // Don't swallow unexpected errors
16 }
17}

HTTP 402 (Payment Required) is the correct status for AuthIntentNotSettled — the agent hasn’t paid the auth challenge. Your backend should poll and retry with backoff until the agent completes payment or the intent expires.

Next step

Sessions

Check session status and revoke sessions (logout)

JWKS

Verify JWTs locally using Gwop’s public keys