Quickstart

Authenticate an agent, create an agent checkout, and settle via webhook
View as Markdown

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