Skip to main content
This is the core merchant flow — create an invoice, wait for payment, fulfill when the webhook fires.

Install the SDK

npm install gwop-checkout
TypeScript types included. Zero runtime dependencies.

Set your environment variables

.env
GWOP_CHECKOUT_API_KEY=sk_m_...   # from Dashboard → Settings
GWOP_WEBHOOK_SECRET=whsec_...    # from Dashboard → Settings

Initialize the client

import { GwopCheckout } from 'gwop-checkout';

const gwop = new GwopCheckout({
  merchantApiKey: process.env.GWOP_CHECKOUT_API_KEY,
});

Create an invoice

const invoice = await gwop.invoices.create(
  {
    amount_usdc: 5_000_000, // $5.00 USDC (6 decimal places)
    description: 'Pro plan — 1 month',
    metadata: { customer_id: 'cust_123' },
  },
  { idempotencyKey: 'order_abc_v1' }, // safe to retry
);

console.log(invoice.id);              // 'inv_...'
console.log(invoice.public_invoice_id); // public-facing ID
console.log(invoice.status);          // 'OPEN'
console.log(invoice.expires_at);      // ISO 8601 timestamp

What happens behind the scenes

When you create an invoice, Gwop:
  1. Generates x402 payment endpoints on Base and Solana. Any x402-compatible agent can pay without custom integration.
  2. Starts the expiry clock. Default TTL is 15 minutes. Configurable via expires_in_seconds (60–86400).
  3. Returns the invoice with a status of OPEN, ready for payment.

Create options

FieldTypeRequiredDescription
amount_usdcnumberYesAmount in USDC minor units. 1_000_000 = $1.00.
descriptionstringNoHuman-readable label (max 500 chars).
metadataobjectNoArbitrary key-value pairs (max 1024 bytes).
metadata_publicbooleanNoExpose metadata in the public invoice view.
expires_in_secondsnumberNoInvoice TTL in seconds (60–86400). Default: 900.

Idempotency

Pass an idempotencyKey to safely retry failed requests. If the same key is sent again, Gwop returns the original invoice instead of creating a duplicate.
const invoice = await gwop.invoices.create(
  { amount_usdc: 10_000_000 },
  { idempotencyKey: 'order_xyz_v1' },
);

Handle the webhook

When payment settles on-chain, Gwop sends a signed invoice.paid event to your webhook URL.
import express from 'express';
import { GwopCheckout } from 'gwop-checkout';

const gwop = new GwopCheckout({
  merchantApiKey: process.env.GWOP_CHECKOUT_API_KEY,
});

const app = express();

app.post(
  '/webhooks/gwop',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const event = gwop.webhooks.constructEvent(
      req.body.toString('utf8'),
      req.header('x-gwop-signature'),
      process.env.GWOP_WEBHOOK_SECRET!, // whsec_...
    );

    await gwop.webhooks.dispatch(event, {
      invoicePaid: async (evt) => {
        // evt.data.invoice_id   — the invoice that was paid
        // evt.data.tx_hash      — on-chain transaction hash
        // evt.data.payment_chain — 'solana' | 'base'
        await fulfillOrder(evt.data.invoice_id);
      },
      invoiceExpired: async (evt) => {
        await cancelPendingOrder(evt.data.invoice_id);
      },
    });

    res.json({ ok: true });
  },
);
Pass the raw body bytes to constructEvent. If JSON parsing runs before signature verification, the signature check will fail. Use express.raw() as shown above.

Invoice lifecycle

Once created, an invoice moves through these statuses:
StatusTerminalMeaning
OPENNoAwaiting payment.
PAYINGNoPayment in progress — do not create a new invoice.
PAIDYesSettlement confirmed on-chain.
EXPIREDYesTTL reached before payment.
CANCELEDYesCanceled by merchant via gwop.invoices.cancel().

Retrieve an invoice

Check the current state of any invoice. This is a public endpoint — no API key required.
const invoice = await gwop.invoices.retrieve('inv_abc123');
console.log(invoice.status);        // 'OPEN' | 'PAYING' | 'PAID' | ...
console.log(invoice.payment_methods); // x402 payment options per chain

Poll for payment (alternative to webhooks)

If you can’t receive webhooks, poll with waitForStatus:
const paid = await gwop.invoices.waitForStatus('inv_abc123', 'PAID', {
  timeoutMs: 60_000,
  intervalMs: 1_000,
});

console.log(paid.paid_tx_hash); // on-chain transaction hash
Uses exponential backoff with jitter. Throws WAIT_TIMEOUT if the timeout is reached.

Next steps