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
  • Error shape
  • Error codes
  • Error handling
  • Common patterns
  • Retry on rate limit
  • Check for specific error codes
Reliability

Errors

Error handling, error codes, and error classes
||View as Markdown|
Was this page helpful?
Edit this page
Previous

Verify Webhooks

Next

SDK Reference

Built with

Error shape

All errors follow a consistent shape with UPPER_SNAKE_CASE error codes. The SDK exports constants and a helper so you never need to string-match:

1import { ErrorCode, isGwopError, ErrorResponse } from "@gwop/sdk/errors";
2
3try {
4 await gwop.invoices.create({ body: { amountUsdc: -1 } });
5} catch (err) {
6 // Match on error code — access statusCode, headers, body
7 if (isGwopError(err, ErrorCode.ValidationError)) {
8 console.log(err.statusCode); // 400
9 }
10
11 // Access structured error fields — use instanceof ErrorResponse
12 if (err instanceof ErrorResponse) {
13 console.log(err.error.code); // "VALIDATION_ERROR"
14 console.log(err.error.message); // "amount_usdc must be at least 1 (0.000001 USDC)"
15 }
16}

Error codes

Build your error handling against codes, not HTTP status codes or message strings. Codes are stable across SDK versions.

CodeConstantStatusMeaningRecommended Action
UNAUTHORIZEDErrorCode.Unauthorized401Invalid, revoked, or missing API keyCheck GWOP_MERCHANT_API_KEY. Do not retry
FORBIDDENErrorCode.Forbidden403Valid key but merchant account not activeContact Gwop support. Do not retry
VALIDATION_ERRORErrorCode.ValidationError400Request body failed validationFix request payload. Use instanceof ErrorResponse to access err.error.details
INVOICE_NOT_FOUNDErrorCode.InvoiceNotFound404Invoice doesn’t exist or not visible to this merchantVerify invoice ID
INVOICE_CANCEL_NOT_ALLOWEDErrorCode.InvoiceCancelNotAllowed400Cannot cancel — invoice is not OPENCheck invoice status before canceling
AUTH_INTENT_NOT_SETTLEDErrorCode.AuthIntentNotSettled402Agent hasn’t paid the auth challenge yetPoll with backoff until paid or expired
AUTH_INTENT_NOT_FOUNDErrorCode.AuthIntentNotFound404Auth intent doesn’t existVerify intent ID or create a new one
AUTH_INTENT_EXPIREDErrorCode.AuthIntentExpired409Auth intent TTL exceededCreate a new auth intent
AUTH_INTENT_USEDErrorCode.AuthIntentUsed409Auth intent already exchanged for a JWTUse the JWT from the first exchange
SESSION_NOT_FOUNDErrorCode.SessionNotFound404Session doesn’t existSession may have been revoked or expired
IDEMPOTENCY_CONFLICTErrorCode.IdempotencyConflict409Idempotency key reused with different parametersUse a fresh crypto.randomUUID()
RATE_LIMITEDErrorCode.RateLimited429Too many requestsBack off. Check Retry-After header

Error handling

The SDK provides two ways to handle errors:

1import { ErrorCode, isGwopError, ErrorResponse } from "@gwop/sdk/errors";
2
3try {
4 await gwop.invoices.create({ body: { amountUsdc: 1_000_000 } });
5} catch (err) {
6 // Recommended — works across all SDK methods
7 if (isGwopError(err, ErrorCode.ValidationError)) {
8 err.statusCode; // 400
9 }
10
11 if (isGwopError(err, ErrorCode.RateLimited)) {
12 err.headers.get("retry-after"); // When to retry
13 }
14
15 // Manual access — when you need the full ErrorResponse type
16 if (err instanceof ErrorResponse) {
17 err.error.code; // UPPER_SNAKE_CASE error code
18 err.error.message; // Human-readable description
19 err.statusCode; // HTTP status code
20 }
21
22 // Any SDK HTTP error
23 if (isGwopError(err)) {
24 err.statusCode; // HTTP status code
25 err.headers; // Response headers
26 err.body; // Raw response body
27 }
28}

Common patterns

Retry on rate limit

1import { ErrorCode, isGwopError } from "@gwop/sdk/errors";
2
3async function createInvoiceWithRetry(amount: number, maxRetries = 3) {
4 const idempotencyKey = crypto.randomUUID();
5
6 for (let attempt = 0; attempt < maxRetries; attempt++) {
7 try {
8 return await gwop.invoices.create({
9 idempotencyKey,
10 body: { amountUsdc: amount },
11 });
12 } catch (err) {
13 if (isGwopError(err, ErrorCode.RateLimited)) {
14 const retryAfter = err.headers.get("retry-after") ?? "60";
15 await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
16 continue;
17 }
18 throw err; // Don't retry client errors
19 }
20 }
21 throw new Error("Max retries exceeded");
22}

Check for specific error codes

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 and retry
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 {
13 throw err;
14 }
15}