Skip to content

NullSwap Merchant Docs

NullSwap lets your backend accept crypto payments. You ask for a deposit address, show it to your customer, and we tell you over a webhook when the money has actually arrived and cleared screening.

Everything on this site describes the merchant API — a machine-to-machine HTTP API authenticated with a single API key. There is no browser flow, no OAuth and no session.

https://api.pay.nullswap.com

The same API also publishes an interactive Swagger reference at api.pay.nullswap.com/docs, generated from the API itself. Use it to check an exact field name or try a call; use this site to understand the flows. See Outside the version prefix.

Two ways to take money

The API offers two collection models. They are independent; use either or both.

Invoice Static wallet
Address belongs to one order one customer, forever
Expected amount stated up front none — you reconcile whatever arrives
Expires yes, 60–604800 seconds never
Tells you when paid webhooks no webhooks — you poll
Screening per payment, before it counts per payment, before it is swept
Reaches your balance swept automatically, or by you with autoSweep: false swept automatically
Use it for checkouts, one-off orders account top-ups, recurring deposits

Start with invoices. Reach for static wallets when you need a long-lived per-customer address rather than a per-order one.

The invoice flow end to end

sequenceDiagram
    autonumber
    participant B as Your backend
    participant N as NullSwap
    participant C as Customer
    participant W as Your webhook endpoint

    B->>N: POST /api/v1/invoices (chain, token, expectedAmount)
    N-->>B: 201 { id, depositAddress, expiresAt, status: WAITING }
    B->>C: show depositAddress + exact amount, until expiresAt
    C->>N: sends the tokens on chain

    N->>W: invoice.payment_detected · status PAYMENT_DETECTED
    Note over W: unconfirmed — show progress, ship nothing

    N->>N: confirmation depth reached, AML screening runs
    N->>W: invoice.payment_confirmed · payment CONFIRMED
    N->>W: invoice.paid · status PAID, paidAmount >= expectedAmount
    Note over W: this is the fulfilment signal

    W-->>N: 2xx

Steps 5, 7 and 8 are webhook deliveries. Polling GET /api/v1/invoices/{invoiceId} returns the same state, but it is a reconciliation tool for when your endpoint has been down — not the integration.

After invoice.paid the money is still on the deposit address. It is swept onto your balance — automatically, or when you ask on an invoice created with autoSweep: false — and you withdraw it from there. See moving funds to your balance. The sweep is credited less the merchant fee and, for a token, the network fee of moving it. The sweep also moves the invoice on to SWAPPING and then COMPLETED, with invoice.swapping and invoice.completed.

Getting started

  1. Get an API key. In the cabinet, the merchant's owner presses Issue a key under Settings → API key and copies the key straight away: it is shown only once. It starts with sk_live_. Keys, IP whitelists and webhook subscriptions are all created there by a person; the API never creates or changes them. See Authentication.
  2. Resolve a chain and a token. GET /api/v1/chains and GET /api/v1/tokens give you the UUIDs an invoice is addressed with. Cache them. See Chains and tokens.
  3. Create an invoice. POST /api/v1/invoices. The response carries depositAddress — show it, or its QR code, to your customer. On RIPPLE show the mandatory depositTag with it. See Invoices.
  4. Handle the webhook. Verify the HMAC signature, deduplicate on the delivery id, answer 2xx quickly, and fulfil on invoice.paid. See Webhooks.

Fulfil on invoice.paid, never on invoice.payment_detected

invoice.payment_detected means a transaction is visible on chain. It has not been confirmed and it has not been screened, so it can still be reorged away or flagged. Goods released on it may end up unpaid for.

Common mistakes

Amounts are strings, never JSON numbers. Every amount crosses the wire as a decimal string of an integer in the token's base units, paired with the token's decimals. 25.50 USDT on TRON is "25500000" with decimals: 6. See Amounts.

Errors are identified by a code, not by prose. The body carries a stable machine-readable code in message; branch on that, never on the HTTP status alone and never on wording.

{
  "statusCode": 422,
  "message": "INVALID_INVOICE_AMOUNT",
  "timestamp": "2026-01-01T00:00:00.000Z"
}

See Errors for the full list.

Where to go next

  • Authentication

    The API key, IP whitelisting, and what a blocked merchant can still do.

  • API conventions

    Amounts, pagination, filtering, timestamps, and which calls are safe to retry.

  • Invoices

    Creating one, the status lifecycle, under- and overpayment, expiry, and sweeping funds to your balance.

  • Invoice payments

    Individual deposits, screening, and what to do with a payment you did not expect.

  • Static wallets

    Long-lived per-customer addresses and the payments that land on them.

  • Webhooks

    The event catalogue, payload format, signature verification, retries and ordering.

  • Chains and tokens

    The catalogue every other route is addressed in terms of.

  • Balances and withdrawals

    Reading your balance, the merchant fee, the withdrawal fee, and paying yourself out.

  • Errors

    Every error name, what raises it, and whether retrying helps.