Documentation

Everything you need to accept non-custodial crypto payments on your own site.

Quick start

  1. 1

    Create an account

    Sign up at pay.zynost.com and enable merchant mode with your own EVM extended public key (xpub) — a public key from any wallet you control, never a private key or seed phrase.

  2. 2

    Copy your API key

    It's shown once when you enable merchant mode, and again any time you regenerate it from Dashboard → API Keys.

  3. 3

    Create your first checkout

    One request, shown below. The response includes a unique receive address derived from your xpub — money sent there goes straight to your own wallet.

bash
curl -X POST https://api.zynost.com/api/v1/checkout \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_usd": 9.99, "order_reference": "order_123"}'

Authentication

Every public API call is authenticated with a Bearer API key — the same one from your dashboard. There is no OAuth flow, no client secret, no separate sandbox key.

Authorization: Bearer zg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Regenerating gives you a new key immediately, but your old one keeps working for a 24-hour grace period rather than dying on the spot — rotate it in your dashboard, then update every server/service that uses it at your own pace before the old one quietly stops working. Keep your key server-side only — never ship it in client-side JS.

Creating checkouts

POST /v1/checkout creates a new order and mints a fresh receive address just for it — never reused.

Request body
{
  "amount_usd": 9.99,
  "order_reference": "order_123",
  "description": "Pro plan (optional)"
}
Response
{
  "id": "169bd00f-...",
  "order_reference": "order_123",
  "amount_usd": 9.99,
  "evm_address": "0x9af24e02a1e5e75d2838668151dce74a4c2744ab",
  "solana_address": null,
  "status": "pending",
  "paid_chain": null,
  "paid_asset": null,
  "paid_amount": null,
  "expires_at": "2026-07-29T02:38:30Z",
  "checkout_url": "https://api.zynost.com/checkout/169bd00f-..."
}

Redirect your customer to checkout_url for a hosted payment page, or build your own UI and poll status yourself:

GET /v1/checkout/{id}

Automatic confirmation requires at least 99% of the invoiced amount in a supported asset on Ethereum, BSC, Polygon or Solana and independently verified finalized evidence. Send within 45 minutes. A further 30-minute observation window allows network finality. Solana transfers must include the invoice reference from the Solana Pay request.

Gasless checkout

A customer who holds stablecoins but no native gas token (BNB, ETH, MATIC) normally can't send a single transaction — they're stuck. Gasless checkout, shown as a "Pay gasless" button on the hosted checkout page automatically for pending BSC orders, removes that wall entirely: Zynost sponsors the network fee for the payment itself, end to end, no native token ever required.

How it works

  1. 1

    One free signature derives a personal smart wallet

    The customer's existing wallet (MetaMask, etc.) signs one fixed message — no gas, nothing broadcast. That signature deterministically derives a private key for an ERC-4337 smart account (eth-infinitism's audited SimpleAccount). Same wallet, same message, same key, every time — nothing to lose if local storage clears, because nothing is ever stored.

  2. 2

    The customer funds that smart wallet

    A normal stablecoin transfer to the address shown — from an exchange withdrawal, another wallet, or anywhere else USDT/USDC comes from. Whoever originates that transfer pays its network fee, exactly like sending to any address; gasless checkout doesn't change that step, it removes the one after it.

  3. 3

    Zynost sponsors the actual payment

    Once funds are detected, the customer signs one more free message approving the exact transfer. Zynost's Paymaster contract then pays the real network fee to move funds from the smart wallet to your evm_address — the same address your existing payment detection and webhooks already watch, so the order settles with zero extra integration work on your side.

The fee only ever applies once — not per payment

The smart wallet address is derived from the customer's owner key alone, never from an order — so it's the same addressacross every future gasless checkout they ever make on Zynost Pay, on any merchant. Fund it once, and every subsequent payment drawn from that balance is sponsored, forever — no repeated funding step, no repeated fee. The only case with a real fee at all is the customer's very first deposit into it, and even that step costs them nothing if it arrives from a source that pays its own network fee (an exchange withdrawal being the common case).

Using it without the hosted checkout page

The hosted checkout page wires all of this up automatically. Building your own checkout UI instead? Drive the same order-scoped, unauthenticated endpoints directly — no API key ever touches the browser, every response is scoped to one order, and the server alone decides where funds go and how much, from its own on-chain checks.

EndpointPurpose
GET /v1/checkout/{id}/gasless/init?owner=0x..Returns the customer's counterfactual smart wallet address for a derived owner key — safe to show before it's ever deployed on-chain.
GET /v1/checkout/{id}/gasless/status?smart_account=0x..Polls the real on-chain balance at that address. Poll this every few seconds while waiting for the customer to fund it.
POST /v1/checkout/{id}/gasless/prepareRe-verifies funding, builds the sponsored transfer, and returns a user_op_hash for the customer's wallet to sign.
POST /v1/checkout/{id}/gasless/submitAttaches the customer's signature and relays the sponsored payment on-chain.

BSC-mainnet only for now. On other chains, or if gasless checkout isn't configured, these endpoints return 503 — always safe to call speculatively and fall back to the normal address-based flow.

Webhooks

Set a webhook URL from Dashboard → Webhooks. We POST here the instant a payment confirms, signed with your webhook secret so you can verify it really came from us.

Payload
{
  "event": "checkout.confirmed",
  "order_id": "169bd00f-...",
  "order_reference": "order_123",
  "amount_usd": 9.99,
  "paid_amount": 9.99,
  "paid_asset": "USDT",
  "paid_chain": "bsc",
  "paid_at": "2026-07-29T02:41:10Z"
}

Verify the X-Zynost-Signature header — same HMAC-SHA256 check, whatever language your backend runs:

python
import hmac, hashlib

def verify(payload_bytes: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
php
<?php
function verify(string $payloadBytes, string $signature, string $secret): bool {
    $expected = hash_hmac('sha256', $payloadBytes, $secret);
    return hash_equals($expected, $signature);
}
node.js
const crypto = require("crypto");

function verify(payloadBytes, signature, secret) {
  const expected = crypto.createHmac("sha256", secret).update(payloadBytes).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Errors

Standard HTTP status codes, JSON body with a detail string.

CodeMeaning
401Missing or invalid API key
400Invalid request body, or nothing owed (billing endpoints)
402Free-plan order limit reached — upgrade or switch billing model
404Checkout or invoice not found

Billing models

Every merchant picks one, and can switch (from Dashboard → Settings):

Flat

Free up to 50 checkouts/month. Upgrade to Pro ($19/mo) for unlimited — paid through this same gateway, with Zynost as the payee.

Pay-as-you-go

No checkout cap. 0.3% of what actually gets paid each month, settled the same way — through the gateway itself.

A billing-mode switch takes effect at the start of your next cycle, never mid-cycle — so it can never be used to dodge a limit you just hit.

Withdrawing funds

There is no withdraw endpoint, because there's nothing to withdraw from us — every payment lands directly at an address derived from your own xpub, under your control the instant it arrives. Since each checkout gets its own fresh address, use the free sweep tool below to consolidate all of them into one wallet whenever you want.

1. Download it

Two files, also available from Dashboard → Withdraw:

2. Install and run

bash
pip install -r requirements-offline-tools.txt
python sweep_via_api.py

3. What it asks you, in order

  1. 1

    Gateway API URL

    Just press Enter — the default is correct unless we've told you otherwise.

  2. 2

    Your API key (zg_live_...)

    From Dashboard → API Keys. Only shown once when created/regenerated — if you don't have it saved, regenerate a new one there first (your old key stays valid for 24 hours, so nothing you have deployed elsewhere breaks while you update it).

  3. 3

    Your 24-word mnemonic

    The same one behind the xpub you gave us. Typed visibly — hidden input proved unreliable across terminals — so make sure no one can see your screen. It's verified against your on-file xpub before anything else happens, and never written to disk or sent anywhere but the public chain RPCs this script calls directly.

  4. 4

    A gas-reserve address is shown

    Same address every run. Fund it once per chain you use (any amount of that chain's native token, e.g. BNB) and the script auto-tops-up whichever order address is short on gas before sweeping it — no more manual top-ups per order.

  5. 5

    Destination EVM address

    Where everything gets consolidated — double-check this before hitting Enter, it's validated as a well-formed address but the script has no way to know it's really yours.

  6. 6

    once or auto

    once does a single pass and exits. auto re-checks and sweeps every 4 hours by itself — leave it running in a screen/tmux session, a systemd service, or nohup ... &.

Every sweep is verified on-chain before it's reported

A transaction hash only means the network accepted it for broadcast — it says nothing about whether it actually succeeded. This tool waits for the real receipt and only reports SWEPT once it confirms with a success status on-chain; a reverted transaction is reported as failed, with the reason, instead of silently being called done. Safe to re-run any time either way — already-swept addresses just read as zero balance and get skipped.

Automating it without retyping every time

Set SWEEP_API_URL, SWEEP_API_KEY, and/or SWEEP_DESTINATION as environment variables and the matching prompt is skipped (still validated the same way). The mnemonic is never accepted this way on purpose — that one you always type yourself.

PowerShell
$env:SWEEP_API_KEY = (Get-Content path\to\your_key.txt -Raw).Trim()
$env:SWEEP_DESTINATION = "0xYourOwnWalletAddress"
python sweep_via_api.py

FAQ

I don't have an xpub — where do I get one?

An xpub (extended public key) comes from any BIP32/BIP44 HD wallet — it's the standard Ethereum derivation path (m/44'/60'/0'), and every major wallet that supports "accounts" has one under the hood. A hardware wallet is the safest source: in Trezor Suite, open an Ethereum account → Account details → "Show public key". If you'd rather use a fresh software wallet dedicated just to receiving Zynost Pay payments, generate one offline with iancoleman/bip39 (download it and run it disconnected from the internet) and read off the account xpub for that derivation path. Either way: never type a real seed phrase into an online tool, and never share your private key or seed phrase with anyone, including us — we only ever ask for the public key.

Is my xpub safe to share?

An xpub derives receive addresses but cannot sign payments by itself. Treat it as sensitive financial metadata because it exposes linked addresses. Never share child private keys alongside an xpub. We do not ask for private keys or seed phrases.

What if a customer underpays?

We accept verified transfers totalling at least 99% of the invoiced amount. Underpayments stay pending — there's no automatic partial fulfillment.

Do you support recurring billing?

Not built-in yet — call POST /v1/checkout again each cycle from your own backend to build recurring billing on top of one-time invoices.

Which chains and assets?

USDT and USDC on Ethereum, BSC, and Polygon (one address covers all three), plus USDT and USDC on Solana with a unique invoice reference. The hosted Solana checkout requests USDC. API integrations receive separate USDC and USDT Solana Pay URLs.

When is an order safe to fulfil?

Wait for a verified checkout.confirmed webhook or a paid status from the API. EVM confirmation requires finalized transfer receipts with independent RPC agreement. Solana confirmation requires a finalized transfer carrying that invoice's reference. A submitted transaction or wallet balance alone is not confirmation.

How should a customer pay on Solana?

Use Connect Wallet or the complete Solana Pay QR/link returned in solana_payment_urls. Keep the reference parameter. A plain address transfer or exchange withdrawal without the reference cannot be automatically attributed. Do not send again to fix a pending status. Contact the merchant with the transaction signature instead.

What happens when the checkout timer reaches zero?

Stop sending. An on-time transfer may still be waiting for finality. We continue observing for 30 minutes after the send deadline but only accept transfers made within the original invoice window. Late transfers or provider-history limitations require merchant support.

With gasless checkout, does the customer really never pay a fee?

Eligible BSC payments can use gas sponsorship while the service and gas deposit are available. Wallet funding may still incur blockchain or exchange withdrawal fees. Ordinary wallet transfers and Solana payments require the relevant network fee. Gas sponsorship does not mean every step is free or guaranteed to succeed.

Can a customer reuse their gasless smart wallet across different merchants?

Yes — it's derived from their own wallet's signature alone, not from any order or merchant, so the exact same address and balance carry over to every gasless checkout they make anywhere on Zynost Pay.