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
  • What happens when you create an agent checkout
  • Install
  • Environment
  • What you have now
  • Keep going
Get Started

Quickstart

Authenticate an agent, create an agent checkout, and settle via webhook
||View as Markdown|
Was this page helpful?
Edit this page
Previous

Gwop

Next

Agent Auth Overview

Built with

In this quickstart, you will wire the core Gwop backend flow:

  • authenticate an agent by wallet
  • create an agent checkout
  • see the headless x402 payment rails Gwop returns for the agent
  • validate the signed invoice.paid webhook

You should be able to go from install to working code in under 5 minutes.

What happens when you create an agent checkout

When your backend calls invoices.create(), Gwop returns a headless x402 checkout session built for agents.

That session includes x402 payment URLs for every supported chain. The agent fetches the public invoice, discovers the available payment methods, picks a chain, and pays the session-scoped x402 URL — exactly the same way it would pay any other x402 endpoint on the internet.

The payment URL is tied to the checkout session, not to your backend. The agent pays Gwop x402 generated URL directly, Gwop settles the session, and your backend receives a signed webhook confirming the sale.

Your Backend ── create invoice ──> Gwop ── checkout session ──> Agent
│
x402 payment (Base) <───┘
x402 payment (Solana) <─┘
│
webhook: invoice.paid <── settle ┘

Your backend keeps full control of the API surface and business rules. Gwop handles multi-chain settlement, webhook confirmation, and the x402 payment contract — the infrastructure you would otherwise build and maintain yourself. The agent gets a machine-readable checkout with concrete payment options and pays using a standard x402 URL, no special integration required.

If you want the production-minded architecture behind this flow, continue into Integration Patterns.

Install

$npm install @gwop/sdk jose

ESM only. For CommonJS projects, use await import("@gwop/sdk").

Environment

$GWOP_MERCHANT_API_KEY=sk_m_...
$GWOP_WEBHOOK_SECRET=whsec_...
$GWOP_IDENTITY_BASE_URL=https://identity.gwop.io

Create a file called quickstart.ts, then add the code below in order.

1

Bootstrap the client and JWT helper

1import { randomUUID } from "node:crypto";
2import { createServer, IncomingMessage, ServerResponse } from "node:http";
3
4import * as jose from "jose";
5import { Gwop } from "@gwop/sdk";
6
7const gwop = new Gwop({
8 merchantApiKey: process.env.GWOP_MERCHANT_API_KEY!,
9 webhookSecret: process.env.GWOP_WEBHOOK_SECRET!,
10 timeoutMs: 8_000,
11});
12
13const identityRequestOptions = {
14 serverURL: process.env.GWOP_IDENTITY_BASE_URL ?? "https://identity.gwop.io",
15 timeoutMs: 8_000,
16} as const;
17
18let cachedJwks = await gwop.auth.getJwks(identityRequestOptions);
19let getKey = jose.createLocalJWKSet({ keys: cachedJwks.result.keys });
20
21async function verifyAccessToken(accessToken: string) {
22 try {
23 const { payload } = await jose.jwtVerify(accessToken, getKey, {
24 issuer: "https://identity.gwop.io",
25 algorithms: ["RS256"],
26 });
27
28 return {
29 subject: payload.sub,
30 sessionId: payload.sid,
31 };
32 } catch (error) {
33 if (error instanceof jose.errors.JWKSNoMatchingKey) {
34 cachedJwks = await gwop.auth.getJwks(identityRequestOptions);
35 getKey = jose.createLocalJWKSet({ keys: cachedJwks.result.keys });
36 return verifyAccessToken(accessToken);
37 }
38
39 throw error;
40 }
41}

One shared client is enough for invoices and webhook validation. Identity-specific calls stay explicit through identityRequestOptions.

2

Authenticate the agent and verify the JWT locally

1const { result: intent } = await gwop.authIntents.create(
2 {
3 idempotencyKey: randomUUID(),
4 body: {
5 metadata: {
6 source: "quickstart",
7 },
8 },
9 },
10 identityRequestOptions,
11);
12
13console.log(intent.authIntentId);
14console.log(intent.challenge.paymentMethods);
15
16// After the wallet settles one of the returned payment URLs:
17const { result: token } = await gwop.authIntents.exchange(
18 {
19 authIntentId: intent.authIntentId,
20 idempotencyKey: randomUUID(),
21 },
22 identityRequestOptions,
23);
24
25const verified = await verifyAccessToken(token.accessToken);
26
27console.log(verified.subject); // "{chain}:{address}"
28console.log(verified.sessionId); // revocable session identifier

The agent is now a customer. verified.subject is the permanent account identity — every purchase, credit balance, and entitlement you create from here is scoped to this wallet. First auth from a wallet creates the account; repeat auth returns the existing one.

3

Create an agent checkout

1const { result: invoice } = await gwop.invoices.create({
2 idempotencyKey: randomUUID(),
3 body: {
4 amountUsdc: 5_000_000, // $5.00 USDC
5 description: "Starter plan",
6 },
7});
8
9console.log(invoice.id); // merchant-side UUID — store internally
10console.log(invoice.publicInvoiceId); // payer-facing inv_* — hand to the agent
11console.log(invoice.agentProtocol); // machine-readable checkout session

The publicInvoiceId is what the agent uses to fetch the checkout and discover available x402 payment methods. The agentProtocol payload tells the agent exactly what to do next: fetch the invoice, pick a chain, pay the x402 URL. Your backend keeps id for internal references and cancellation.

When the agent fetches the public invoice, it sees the full checkout session — including one x402 payment method per supported chain:

1"paymentMethods": [
2 {
3 "id": "x402-base",
4 "kind": "x402",
5 "network": "eip155:8453",
6 "chain": "base",
7 "asset": {
8 "symbol": "USDC",
9 "decimals": 6,
10 "contract": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
11 },
12 "payTo": "0xcFf8Dcf2e9562Ec4D77E387902BB52Ea18539215",
13 "amount": 5125000,
14 "amountDisplay": "5.125 USDC",
15 "paymentUrl": "https://agents.gwop.io/v1/invoices/inv_.../x402/base",
16 "recoverUrl": "https://agents.gwop.io/v1/invoices/inv_.../x402/base/recover"
17 },
18 {
19 "id": "x402-solana",
20 "kind": "x402",
21 "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
22 "chain": "solana",
23 "asset": {
24 "symbol": "USDC",
25 "decimals": 6,
26 "contract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
27 },
28 "payTo": "BL24KoQbSate4NkfbGoyqK3ts8ck8fjiXrM3K3BJ9Hpr",
29 "amount": 5125000,
30 "amountDisplay": "5.125 USDC",
31 "paymentUrl": "https://agents.gwop.io/v1/invoices/inv_.../x402/solana",
32 "recoverUrl": "https://agents.gwop.io/v1/invoices/inv_.../x402/solana/recover"
33 }
34]

Each paymentUrl is scoped to the checkout session. The agent picks a chain and pays the URL the same way it would pay any x402 endpoint. Gwop verifies the transaction on-chain and settles the session.

4

Handle the webhook

1function readBody(req: IncomingMessage): Promise<string> {
2 return new Promise((resolve, reject) => {
3 const chunks: Buffer[] = [];
4 req.on("data", (chunk) => chunks.push(chunk));
5 req.on("end", () => resolve(Buffer.concat(chunks).toString()));
6 req.on("error", reject);
7 });
8}
9
10const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
11 if (req.method === "POST" && req.url === "/webhooks/gwop") {
12 const rawBody = await readBody(req);
13
14 const event = await gwop.validateWebhook({
15 request: {
16 body: rawBody,
17 headers: {
18 "x-gwop-signature": req.headers["x-gwop-signature"] as string,
19 "x-gwop-event-id": req.headers["x-gwop-event-id"] as string,
20 "x-gwop-event-type": req.headers["x-gwop-event-type"] as string,
21 "content-type": "application/json",
22 },
23 url: `http://localhost:3000${req.url}`,
24 method: "POST",
25 },
26 });
27
28 if (event.body.eventType === "invoice.paid") {
29 console.log("Paid:", event.body.data.publicInvoiceId);
30 console.log("Chain:", event.body.data.paymentChain);
31
32 // Activate the subscription, grant credits, or unlock the purchase here.
33 }
34
35 res.writeHead(200);
36 res.end("ok");
37 }
38});
39
40server.listen(3000, () => console.log("Listening on :3000"));

Use the exact raw request body bytes for verification. If you parse and re-stringify JSON first, the HMAC signature will not match.

What you have now

  • one shared Gwop client for your backend
  • wallet auth plus local JWT verification
  • an agent checkout with x402 payment rails the agent can use immediately
  • signed webhook validation for fulfillment

That is the full merchant-side loop: create a checkout, let the agent pay over x402, and settle via webhook. The next layer is architecture — where these pieces live in your routes, services, and adapters.

Keep going

Integration Patterns

See how to turn this flow into a production-minded backend architecture

Two Invoice IDs

Learn why Gwop invoices have both merchant and payer-facing identifiers

Session vs Token

Learn when local JWT verification is enough and when to check the session

Wallet Identity

Learn why {chain}:{address} is the durable customer identity