All docs Reference

HTTP API

Every endpoint exposed by the protocol. REST for humans, JSON-RPC over MCP for tools, x402 for autonomous agents. All responses are JSON, all writes are idempotent, all webhooks are HMAC-signed.

v1 · stable Base URL: https://zettapay.dev · Auth: Authorization: Bearer <api_key>

Authentication

Authenticate every server-to-server request with your API key as a bearer token. Keys are issued from the merchant dashboard after registration. Treat keys like passwords — rotate immediately on compromise.

Authorization: Bearer zp_live_
Content-Type: application/json

Public endpoints (/healthz, /api/status, embed asset routes) do not require auth.

Idempotency

All POST writes accept an Idempotency-Key header (≤128 chars). Replays return the original response with the original status. Use a fresh UUID per logical operation, persist it alongside the call, and re-send on retry.

Idempotency-Key: 9f4d1c20-7d3b-4e0a-9c0f-3f9b2a8b1234

Errors

Errors return a non-2xx status and a stable JSON envelope. The code field is machine-readable; message is human-readable and may change. Never branch on message.

{
  "error": {
    "code": "invalid_amount",
    "message": "Field \"amount\" must be a positive number"
  }
}
StatusCode familyMeaning
400invalid_*Request validation failed.
401unauthorizedMissing or invalid API key.
403forbiddenKey lacks access to this resource.
404not_foundMerchant, payment, or invoice does not exist.
405method_not_allowedHTTP verb not supported on this path.
409conflictIdempotency key reused with a different payload.
429rate_limitedPer-key rate limit exceeded — see below.
5xxinternal / upstreamRetry with exponential backoff.

Rate limits

Sliding window per API key. Limits are advertised in response headers and apply per endpoint family.

X-RateLimit-Limit:     60
X-RateLimit-Remaining: 57
X-RateLimit-Reset:     1715900400

Free tier: 60 req/min per key. Beta cohorts and self-hosted deployments raise the ceiling — contact us for production limits.

Merchants/api/merchants/*

POST /api/merchants/register

Register a merchant. Returns a deterministic merchant id, an API key handle, and your webhook receipt URL. Idempotent.

Request body

FieldTypeNotes
namerequiredstringMerchant display name, ≤120 chars.
walletAddressrequiredstringBase58 Solana pubkey where USDC settles. Wallet-less — no connect required.
emailrequiredstringOperational notices, magic-link login.
webhookUrloptionalstringHTTPS URL. Must serve a valid certificate.

Example

curl -X POST https://zettapay.dev/api/merchants/register \
  -H "Authorization: Bearer zp_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Acme Robotics",
    "walletAddress": "4Nd1mYG6X9wQ…ZkR2",
    "email": "ops@acme.example",
    "webhookUrl": "https://acme.example/zp/webhook"
  }'
POST GET /api/merchants/onboard

Self-service onboarding state used by /signup. POST creates a merchant from the signup flow, GET returns the checklist + dashboard link + ready-to-paste embed snippet.

GET /api/merchants/:merchant

Public merchant card: handle, wallet, accepted currencies. Used by the embed widget to resolve data-merchant.

GET /api/merchants/:merchant/analytics

TPV, MRR, conversion, top customers, and 30-day trends. Powers the per-merchant dashboard. Aggregates only — no PII.

GET /api/merchants/:merchant/payments

Paginated payment history scoped to a single merchant. Pair with ?limit= and ?cursor=.

QueryDefaultNotes
limit50Max 200.
cursorOpaque, returned in next.
statuspending · confirmed · failed
GET /api/merchants/:merchant/payouts

Settled batches with on-chain signatures. Powers the /dashboard/payouts page.

POST GET DELETE /api/merchants/:merchant/keys

Rotate, list, or revoke API keys. Newly issued keys are returned once in cleartext; persist immediately.

PATCH /api/merchants/:merchant/settings

Update webhook URL, email, brand color, default currency. Audit-logged.

Payments/api/pay · /api/payments

POST /api/pay

Create a payment intent. Returns a Solana Pay URI and an invoice id. The payer scans the QR (or pastes the address) from any Solana wallet — no wallet connect, no extension. Settlement is observed on-chain and dispatched as a webhook.

Request body

FieldTypeNotes
merchantIdrequiredstring≤64 chars.
amountrequirednumberUSDC, positive, ≤1,000,000.
currencyoptionalstringDefault USDC.
payerWalletoptionalstringBase58 Solana pubkey. Used to scope the invoice + populate analytics.
metadataoptionalobjectFree-form JSON returned on webhook. Treat as semi-public.

Response

{
  "id": "inv_01H8XK…",
  "status": "pending",
  "amount": 10,
  "currency": "USDC",
  "checkoutUrl": "https://zettapay.dev/checkout/inv_01H8XK…",
  "solanaPayUri": "solana:4Nd1mYG6X9wQ…?amount=10&spl-token=…&reference=…",
  "expiresAt": "2026-05-16T20:30:00Z"
}
GET /api/payments

Account-wide payment listing for the authenticated key. Use the per-merchant variant for narrower scopes.

GET /api/analytics/:merchant

Aggregate timeseries: daily TPV, average ticket, distinct payers, conversion. JSON output, ready for charting.

POST /api/simulate/:merchant

Devnet-only. Emits a synthetic settlement, fires the merchant webhook, and inserts a row into payments. Convenient for end-to-end webhook tests without touching the network.

WebhooksStripe-grade delivery

Delivery semantics

Webhooks are POSTed to the URL registered on the merchant. We retry up to three times with exponential backoff (10s, 60s, 5m). Deliveries are queued, ordered per invoice, and replayable from the dashboard for 90 days. Webhook URLs must be HTTPS.

Event payload

{
  "id": "evt_01H8XK…",
  "type": "payment.confirmed",
  "created": "2026-05-16T19:42:11Z",
  "data": {
    "invoiceId": "inv_01H8XK…",
    "merchantId": "@acme-robotics",
    "amount": 10,
    "currency": "USDC",
    "payer": "6tK…dpL",
    "signature": "5x9c…Wq3",
    "metadata": { "orderId": "o_42" }
  }
}

Event types

EventFires when
payment.pendingInvoice created.
payment.confirmedOn-chain confirmation observed.
payment.failedInvoice expired or amount mismatch.
payout.settledDaily batch landed in merchant wallet.
merchant.updatedSettings changed via dashboard or API.

Signature verification

Each delivery includes an X-ZettaPay-Signature header: t=<timestamp>, v1=<hex>. Compute HMAC-SHA256(t + "." + rawBody, webhookSecret) and constant-time compare against v1. Reject deliveries older than 5 minutes to defeat replay.

import { createHmac, timingSafeEqual } from 'crypto';

export function verify(rawBody: string, header: string, secret: string) {
  const [tPart, vPart] = header.split(',').map(p => p.trim());
  const t = tPart.split('=')[1];
  const v1 = vPart.split('=')[1];

  const mac = createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  if (mac.length !== v1.length) return false;
  return timingSafeEqual(Buffer.from(mac), Buffer.from(v1));
}

Replay protection

Every event ships with a stable id. Persist id on receipt and ignore re-deliveries. Combine with the timestamp window to defeat replay attacks. Replays from the dashboard are deliberate and re-use the original event id.

AI Agentsx402 · MCP

x402 — autonomous payments

ZettaPay is an x402 payment provider. When an agent hits a paywalled resource that returns 402 Payment Required, the response advertises a ZettaPay challenge. The agent signs a transaction blob (held in its own wallet, never custodied by ZettaPay) and re-sends the request with X-PAYMENT. The protocol verifies on-chain and unlocks the resource.

HTTP/1.1 402 Payment Required
WWW-Authenticate: X402 realm="ZettaPay",
  amount=0.05, currency="USDC",
  recipient="4Nd1mYG…ZkR2",
  nonce="a3f9…"

Full spec lives at /docs/quickstart#x402.

POST /api/mcp JSON-RPC 2.0

Model Context Protocol endpoint. Exposes payment primitives as tool calls so any MCP-aware host (Claude, GPT, Gemini) can issue invoices, poll status, and stream confirmations without bespoke integration code.

Tools advertised

ToolDescription
create_invoiceMint an invoice and return the Solana Pay URI.
get_invoiceFetch status + on-chain signature for an invoice id.
list_paymentsPaginate confirmed payments for the calling agent identity.
resolve_merchantLook up a merchant card by handle or wallet.

Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_invoice",
    "arguments": { "merchantId": "@acme", "amount": 0.05 }
  }
}

Optional integrationsthird-party · BYO

Third-party fiat onramp

ZettaPay does not ship a built-in fiat onramp. The protocol is non-custodial: the customer must already hold the cryptocurrency they pay with. If a fraction of your audience needs fiat→crypto, embed any onramp you trust (MoonPay, Ramp, Transak, Coinbase Onramp, etc.) alongside the ZettaPay widget — pass the invoice's destination address to the onramp's deposit-address parameter so the purchased crypto lands at the merchant's wallet.

ZettaPay's chain listener detects the deposit on-chain just like any other payment and posts your standard payment.confirmed webhook — there is no special onramp event type. KYC, fees, currencies, and regional restrictions are governed entirely by the onramp provider.

Health & statusunauthenticated

GET /healthz

Liveness probe. 200 when the process is reachable.

GET /ready

Readiness probe. 200 only when DB, RPC, and webhook dispatcher are all reachable.

GET /metrics

Prometheus exposition format. Per-endpoint latency histograms, request counts, webhook delivery counters.

GET /api/status

Component-by-component health snapshot used by the public /status page. Includes an RSS feed at /status/feed.rss.

POST /api/faucet devnet only

Drip devnet USDC to a wallet you control. Rate-limited per IP. See /docs/faucet.

Ready to ship?

Start with the 5-minute quickstart or drop the embed onto your checkout.