Quickstart: Seller

Get paid by humans and agents

Every GenesisPay link renders a hosted checkout for browsers and a machine-readable x402 response for agents — same URL, same payment object, settled in USDC. This guide goes from login to your first paid link.

1. Log in and create a key

GenesisPay is a hosted app: your account lives at the origin you are reading these docs on. Log in there with Google, email, or a wallet — no crypto setup required. Pick “Accept payments”in onboarding: GenesisPay assigns a receiving wallet for your payouts (you can override it in Settings) and walks you through creating a seller API key. You can also issue keys any time on the dashboard's Developers page.

Keys look like pp_sk_... and are shown once — store yours as PEERPAY_SELLER_KEY. You only need a key for programmatic access; links can also be created entirely in the dashboard.

The examples below use two environment variables:

Environment
# The origin your GenesisPay account lives on — the same URL
# you're reading these docs on (e.g. your deployed app URL,
# or http://localhost:3000 in local dev).
export PEERPAY_BASE_URL="https://<your-peerpay-host>"
# The pp_sk_... key from the dashboard's Developers page.
export PEERPAY_SELLER_KEY="pp_sk_your_seller_key"

PEERPAY_BASE_URL is the origin where your GenesisPay account lives — the same URL you are reading these docs on (for the hosted beta, your deployed app URL; for local dev, http://localhost:3000). There is no separate API host: the app, the dashboard, and the API all share this origin. PEERPAY_SELLER_KEY is the pp_sk_... key you just created on the Developers page.

3. Or use the SDK

The legacy PeerPay client wraps the same endpoints for JavaScript and TypeScript — on Node, Edge runtimes, Workers, and Bun.

Terminal
npm install @genesis-tech/peerpay-seller
create-checkout.ts
import { PeerPay } from "@genesis-tech/peerpay-seller";

const peerpay = new PeerPay({
  apiKey: process.env.PEERPAY_SELLER_KEY!, // pp_sk_...
  baseUrl: process.env.PEERPAY_BASE_URL,   // your GenesisPay origin
});

const checkout = await peerpay.checkout.create({
  title: "Market report",
  // Decimal string in `asset` — dollars for USDC, euros for EURC.
  // (`amountUsdc` is the deprecated pre-0.6.0 name for this field.)
  amount: "5.00",
  asset: "USDC",
  linkType: "reusable",
  // Your identifiers, echoed by retrieve() and on every webhook:
  clientReferenceId: order.id,
  metadata: { orderId: order.id, buyerId: user.id },
  returnUrl: "https://shop.example/thanks",
  cancelUrl: "https://shop.example/cart",
});

checkout.publicId; // "abc123"
checkout.payUrl;   // send the payer here

baseUrl is your GenesisPay origin — the same PEERPAY_BASE_URL as above. Omit it only when you are on the hosted facilitator that matches your key mode (pp_sk_test_ / pp_sk_live_). The receiving wallet and network are resolved from the key, so no wallet address appears in your code.

checkout.retrieve(publicId)reads a link's current state. paid is the value to poll on: true from the first on-chain confirmation, for reusable links too.

poll-checkout.ts
import { PeerPayNotFoundError } from "@genesis-tech/peerpay-seller";

try {
  const session = await peerpay.checkout.retrieve(checkout.publicId);

  session.paid;                  // true once >= 1 payment is confirmed on-chain
  session.confirmedPaymentCount; // 0, or how often a reusable link was paid
  session.clientReferenceId;     // "order_1042" — exactly what you sent
  session.metadata;              // { orderId: "A-1042" } | null

  if (session.paid) {
    await fulfil(session.clientReferenceId);
  }
} catch (error) {
  if (error instanceof PeerPayNotFoundError) {
    // Unknown publicId, or one belonging to another account — not a config bug.
  }
  throw error;
}

Polling is the fallback — a webhooktells you the same thing without the loop. Either way, that is the check that decides fulfilment; the payer's return URL is not.

4. Get paid — by anyone

Share payUrl. Humans open it as a normal checkout page and pay with their wallet. Agents request the exact same URL over HTTP and get an x402 payment requirement instead:

Terminal
# The same URL is an x402 endpoint for agents:
curl -i "$PEERPAY_BASE_URL/pay/abc123" -H "Accept: application/json"
# -> 402 Payment Required + PAYMENT-REQUIRED header (x402 V2)

You do nothing extra for the agent side — GenesisPay negotiates content per client and verifies every USDC transfer on-chain before a payment counts. Track payments per link in the dashboard, or register a webhook to be notified when money arrives.

Optional: gate your own API

If you would rather charge for an endpoint you already run, wrap the handler with @genesis-tech/peerpay-seller. The gate emits the 402 Payment Required response, validates incoming PAYMENT-SIGNATUREheaders, and delegates on-chain settlement to GenesisPay's facilitator.

route.ts
import { createPaymentGate, peerPaySettlement } from "@genesis-tech/peerpay-seller";

const gate = createPaymentGate({
  amountUsdc: "0.10",
  payTo: "0xYourReceivingWallet",
  description: "Premium data lookup",
  network: "base-sepolia",
});

// Works with Next.js route handlers, Hono, and Bun.serve.
export const GET = gate.wrap(
  async () => Response.json({ data: "paid content" }),
  {
    verifySettlement: peerPaySettlement({
      facilitatorBaseUrl: process.env.PEERPAY_BASE_URL!,
      apiKey: process.env.PEERPAY_SELLER_KEY!, // pp_sk_...
    }),
  },
);

peerPaySettlement POSTs the signed payment to /api/v1/facilitator/settle with your seller key. GenesisPay broadcasts the EIP-3009 authorization on-chain and verifies the USDC transfer before your handler runs. Once your endpoint is live, consider listing it in discovery so agents can find it.

Pricing models

GenesisPay charges per call: every link and every gated endpoint has one fixed amount that is collected on each paid request. That covers pay-per-call APIs, one-off purchases, and metered access where each request is its own charge.

Subscriptions and metered pay-per-use are covered by payment mandates: the customer signs one gasless spending approval, and you charge per use or per period without a signature per charge — funds still move directly from the payer's wallet to yours. Per-call x402 stays the native fit for agents; mandates add the recurring and metered models on top.