Infrastructure for agent-native commerce. Turn AI agents into customers.
Gwop gives merchants the missing commerce layer for selling to AI agents. Authenticate agents by wallet, sell subscriptions and credits with USDC, and manage customers — all headless, all API-first.
Gwop is designed for headless, agent-native stores — no browser, no UI, no human in the loop:
- Credit-based APIs — Sell prepaid credits, enforce usage limits, track consumption per agent
- Subscription services — Plans with tiered access, daily caps, and model restrictions
- One-time purchases — Reports, datasets, API packages, or any digital good
- Agent marketplaces — Multi-tenant platforms where agents discover, buy, and use services
Live example: AgentRouter (skill.md) — a headless LLM inference store built entirely on Gwop.
Raw x402 gives you stateless per-request payments. Gwop gives you customers:
| Raw x402 | With Gwop |
|---|---|
| Anonymous wallet hits endpoint | Authenticated agent with identity and session |
| Pay per request, every request | Authenticate once, charge upfront, usage flows smoothly |
| No concept of who paid before | Account history, entitlements, plan enforcement |
| Merchant builds auth + billing | One SDK: auth, checkout, webhooks, treasury |
You need a Gwop merchant account to get API keys, merchant wallets, and webhook secrets.
- Apply: merchant.gwop.io/apply
- Email: hello@gwop.io
- X: @gwop_io
npm install @gwop/sdk
# or
yarn add @gwop/sdk
# or
pnpm add @gwop/sdk
# or
bun add @gwop/sdkESM only. For CommonJS projects, use
await import("@gwop/sdk").
Add your credentials to .env — find these in your merchant dashboard under Settings:
GWOP_MERCHANT_API_KEY=sk_m_... # Settings → API Keys → + Create
GWOP_WEBHOOK_SECRET=whsec_... # Settings → Webhook Configuration → Secretimport { Gwop } from "@gwop/sdk";
const gwop = new Gwop(); // reads GWOP_MERCHANT_API_KEY from env
// Create an invoice
const { result: invoice } = await gwop.invoices.create({
idempotencyKey: crypto.randomUUID(),
body: {
amountUsdc: 5_000_000, // $5.00 USDC (6 decimals)
description: "Starter plan — 300 credits",
metadata: { planId: "starter" },
},
});
// Hand the payment URL to the agent
console.log(invoice.publicInvoiceId); // inv_7dbeeaad8ebf...
console.log(invoice.agentProtocol); // Machine-readable payment instructions
// Check payment status
const { result: status } = await gwop.invoices.get({
id: invoice.publicInvoiceId,
});
console.log(status.status); // "OPEN" → "PAYING" → "PAID"Identify agents by wallet using a x402 authentication challenge:
// 1. Create an auth challenge ($0.001 USDC dust payment)
const { result: intent } = await gwop.authIntents.create({
idempotencyKey: crypto.randomUUID(),
});
// 2. Agent pays → exchange for a JWT
const { result: token } = await gwop.authIntents.exchange({
authIntentId: intent.authIntentId,
idempotencyKey: crypto.randomUUID(),
});
console.log(token.accessToken); // RS256-signed JWT
console.log(token.principal.sub); // "base:0x742d..." or "solana:7sSi..."
console.log(token.account.isNewAccount); // true on first authYour backend calls these endpoints with sk_m_* keys. Agents never talk to Gwop directly — only your backend does. See full auth docs for JWT verification, session management, and JWKS.
const gwop = new Gwop(); // reads GWOP_WEBHOOK_SECRET from env
const event = await gwop.validateWebhook({
request: {
body: rawBody,
headers: {
"x-gwop-signature": req.headers["x-gwop-signature"],
"x-gwop-event-id": req.headers["x-gwop-event-id"],
"x-gwop-event-type": req.headers["x-gwop-event-type"],
"content-type": "application/json",
},
url: `https://${req.headers.host}${req.originalUrl}`,
method: "POST",
},
});
switch (event.body.eventType) {
case "invoice.paid": // funds received
case "invoice.expired": // invoice TTL reached
case "invoice.canceled": // merchant canceled
}Use the raw body for verification. Re-stringified JSON will break the HMAC signature. Works in Node.js, Deno, Bun, and edge runtimes.
| Method | Description |
|---|---|
gwop.invoices.create() |
Create an invoice for agent payment |
gwop.invoices.list() |
List invoices with pagination and status filter |
gwop.invoices.get() |
Get the public invoice view (takes publicInvoiceId) |
gwop.invoices.cancel() |
Cancel an open invoice (takes merchant UUID id) |
| Method | Description |
|---|---|
gwop.authIntents.create() |
Create a wallet auth challenge |
gwop.authIntents.exchange() |
Exchange settled intent for JWT (402 if unpaid) |
gwop.authSessions.get() |
Get session status |
gwop.authSessions.revoke() |
Revoke a session (logout) |
gwop.auth.getJwks() |
Fetch JWKS for local JWT verification |
| Method | Description |
|---|---|
gwop.validateWebhook() |
Verify HMAC signature + parse typed event |
All errors use UPPER_SNAKE_CASE codes. The SDK exports constants and a helper so you never need to string-match:
import { ErrorCode, isGwopError } from "@gwop/sdk/errors";
try {
await gwop.invoices.get({ id: "inv_missing" });
} catch (err) {
if (isGwopError(err, ErrorCode.InvoiceNotFound)) {
// handle missing invoice
}
if (isGwopError(err, ErrorCode.RateLimited)) {
// back off and retry
}
if (isGwopError(err)) {
// any SDK HTTP error — access statusCode, headers, body
console.log(err.statusCode);
}
}For manual access without the helper:
import { ErrorCode, ErrorResponse } from "@gwop/sdk/errors";
if (err instanceof ErrorResponse && err.error.code === ErrorCode.InvoiceNotFound) {
// ...
}| Code | Status | Meaning |
|---|---|---|
ErrorCode.Unauthorized |
401 | Invalid, revoked, or missing API key |
ErrorCode.Forbidden |
403 | Valid key but merchant account not active |
ErrorCode.ValidationError |
400 | Request body failed validation |
ErrorCode.InvoiceNotFound |
404 | Invoice doesn't exist or not visible to this merchant |
ErrorCode.InvoiceCancelNotAllowed |
400 | Cannot cancel — invoice is not OPEN |
ErrorCode.AuthIntentNotSettled |
402 | Agent hasn't paid the auth challenge yet |
ErrorCode.AuthIntentNotFound |
404 | Auth intent doesn't exist |
ErrorCode.AuthIntentExpired |
409 | Auth intent TTL exceeded — create a new one |
ErrorCode.AuthIntentUsed |
409 | Auth intent already exchanged for a JWT |
ErrorCode.SessionNotFound |
404 | Session doesn't exist |
ErrorCode.IdempotencyConflict |
409 | Idempotency key reused with different parameters |
ErrorCode.RateLimited |
429 | Too many requests — check Retry-After header |
const gwop = new Gwop({
merchantApiKey: "sk_m_...", // or set GWOP_MERCHANT_API_KEY env var
webhookSecret: "whsec_...", // or set GWOP_WEBHOOK_SECRET env var
timeoutMs: 30_000, // request timeout
debugLogger: console, // or set GWOP_DEBUG=true
});The SDK retries failed requests with exponential backoff automatically. All methods return { headers, result }. See docs.gwop.io for retry configuration, response shapes, and advanced usage.
Every invoice has two identifiers:
| Field | Format | Used by |
|---|---|---|
id |
UUID (ba7bc94a-...) |
Your backend — list, cancel, internal references |
publicInvoiceId |
inv_* (inv_7dbeeaad...) |
Payers and agents — get invoice, payment URLs |
The backend adds a 2.5% platform fee to amountUsdc. A request for 5_000_000 ($5.00) results in an invoice total of 5_125_000 ($5.125). The fee breakdown is injected into metadata automatically.
- Docs: docs.gwop.io
- Apply for access: merchant.gwop.io/apply
- Support: hello@gwop.io
- X: @gwop_io
Built by the Gwop team. MIT License.