Skip to content

Static wallets

A static wallet is an address that belongs to one of your customers for good, rather than to a single order. Nothing expires and nothing states an amount: you hand the address out once and reconcile whatever arrives on it against your own reference.

Use it for account top-ups and recurring deposits. Use an invoice when you need a specific amount for a specific order.

Issue an address

curl -X POST https://api.pay.nullswap.com/api/v1/static-wallets \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "chainType": "EVM", "reference": "customer-42" }'
Field Type Required Notes
chainType enum yes EVM, TRON, SOLANA, BITCOIN, LITECOIN, DASH, DOGECOIN, RIPPLE, TON, MONERO
reference string yes Your own name for the customer. Max 128 characters, counted after surrounding whitespace is trimmed
{
  "id": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "chainType": "EVM",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "destinationTag": null,
  "xAddress": null,
  "reference": "customer-42",
  "createdAt": "2026-01-01T12:00:00.000Z"
}

Two properties of this endpoint:

  • It is idempotent on chainType and reference together. Asking again with the same pair returns the address that pair already has rather than minting a second one; the same reference with another chainType gets that family's own address. The reference is trimmed before it is matched and stored. The call always answers 201, whether it created something or not, so you can call it on every visit instead of storing the address yourself.
  • The address is issued per chain family, not per chain. One EVM address serves Ethereum, Arbitrum, BNB Chain and every other EVM network. A customer needs one address per family, not one per network — which is why this route takes a chainType rather than a chainId.

destinationTag and xAddress are null on every family but RIPPLE; see below.

Status message Cause
400 INVALID_REQUEST_BODY Missing field, an empty reference, or chainType is not one of the enum values
403 MERCHANT_BLOCKED The merchant is blocked
422 INVALID_STATIC_WALLET_REFERENCE reference is only whitespace, or longer than 128 characters once trimmed

Reading addresses

Method Path Purpose
GET /api/v1/static-wallets List, filtered by id, chainType, reference, address
GET /api/v1/static-wallets/{staticWalletId} Read one

reference is the normal way to find an address you issued earlier:

GET /api/v1/static-wallets?reference=customer-42&limit=10
Status message Cause
400 INVALID_PATH_PARAMETER staticWalletId is not a UUID
403 PERMISSION_DENIED The address belongs to another merchant
404 STATIC_WALLET_NOT_FOUND No such address

On RIPPLE the tag is the wallet

On RIPPLE, the XRP Ledger, every static wallet has the same address. What tells your customers apart is the destination tag: each wallet has its own destinationTag, and a payment is credited to the wallet whose tag it carries.

{
  "id": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "chainType": "RIPPLE",
  "address": "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3",
  "destinationTag": 959725,
  "xAddress": "XVMFQQBMhdouRqhPMuawgBMN1AVFToG5NjwwoxoAYuiUyDF",
  "reference": "customer-42",
  "createdAt": "2026-01-01T12:00:00.000Z"
}
Field Notes
destinationTag Integer, 0–4294967295. The tag a payment to address must carry to reach this wallet. Not sequential: show the one returned
xAddress address and destinationTag as one X-address (X…). Paying to it is paying to the pair; it is also what a QR code should encode

Give your customer the address and the tag together, the tag marked as required, or the X-address on its own. The address filter of GET /api/v1/static-wallets matches xAddress too; the plain address of a RIPPLE wallet matches every RIPPLE static wallet you have.

A payment without the right tag reaches none of your wallets

The network refuses a payment to address that carries no destination tag. One with a tag that is not a wallet's is held and is not credited to any wallet automatically — contact support with the transaction hash. A customer who saved only the address has saved half of it.

Payments

curl "https://api.pay.nullswap.com/api/v1/static-wallets/payments?settlement=PENDING&limit=100" \
  -H "x-api-key: sk_live_..."

Without staticWalletId this lists every arrival across all of your addresses, which is what you reconcile against. Filters: id, staticWalletId, status, settlement, plus the usual pagination. Each row of data is one payment:

{
  "id": "a7b8c9d0-e1f2-4345-a678-9b0c1d2e3f45",
  "staticWalletId": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "chainTransactionId": "cc33dd44-ee55-4f66-8077-889900112233",
  "chainTransferId": "dd44ee55-ff66-4077-8188-990011223344",
  "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
  "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
  "fromAddress": "0xfedcba0987654321fedcba0987654321fedcba09",
  "amount": "1500000",
  "status": "CONFIRMED",
  "confirmations": 12,
  "amlStatus": "PASSED",
  "amlResultId": "5c6d7e8f-9a0b-41c2-8d34-5e6f7a8b9c0d",
  "settlement": "SWEEPING",
  "sweepTransferId": "ee55ff66-0077-4188-8299-001122334455",
  "sweepId": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
  "createdAt": "2026-01-01T12:00:00.000Z",
  "updatedAt": "2026-01-01T12:03:00.000Z"
}

Static wallet payments do not emit webhooks

No event in the webhook catalogue covers a static wallet payment. Reconciling them means polling this endpoint. A reasonable job filters on settlement=PENDING plus a createdAt-ordered window and runs every minute or two. The only events a static wallet can cause are conversion.completed and conversion.skipped, when an auto-conversion rule covers its sweep — they report the sweep, not the payments in it.

Native coin we send to the address to pay the network fee of moving your money on is not a payment: it is not listed here and is not swept to your balance.

Watch settlement, not status

A static address collects funds and hands them on to your balance, so what matters is not whether the transaction confirmed but where the money ended up.

Every payment that confirms and clears screening is swept to your balance automatically. There is no autoSweep switch for static wallets. A sweep that cannot be started is retried until it is accepted, and one that fails is raised again a limited number of times.

flowchart TD
    A["Deposit arrives"] --> B["status DETECTED<br/>settlement PENDING"]
    B --> C["Confirmed to the required depth"]
    C --> D{"AML screening"}
    D -- "PASSED / SKIPPED" --> E{"Worth more than<br/>the network fee?"}
    D -- "FAILED" --> F["settlement WITHHELD<br/>money stays on the address"]
    E -- "yes" --> G["settlement SWEEPING<br/>sweepTransferId set"]
    E -- "no, dust" --> H["settlement PENDING<br/>waits for the next payment"]
    H -. "next payment in the same token" .-> E
    G --> I["settlement SWEPT<br/>credited to your balance"]
settlement Meaning
PENDING Not screened yet, or clean but not swept yet — dust, or a sweep still being retried. Nothing has moved.
SWEEPING Clean, and the sweep to your balance has been accepted. sweepTransferId is set
SWEPT The sweep completed and the money is on your balance, less the merchant fee and the network fee of moving it — in another token, if an auto-conversion rule converted it
WITHHELD Flagged by screening, and staying on the static address
status Meaning
DETECTED On chain, not yet confirmed or screened. Never money you have
CONFIRMED Confirmed and screened
FAILED The transaction that carried it reverted or was reorged out

Note that PENDING is ambiguous by design — it covers both "we have not screened this yet" and "it is clean but has not moved yet". Read amlStatus to tell the two apart: null means screening has not run.

A clean payment worth no more than the network fee of moving it is dust. It stays PENDING on the address and is swept together with the next payment in the same token. One sweep moves the token's whole clean amount on the address, so several payments can share one sweepId and one sweepTransferId. sweepId is set once a sweep has been raised for the payment, sweepTransferId once that sweep has been accepted. Once the sweep settles, the fee it withheld is the row with that id in the list of fees withheld — per sweep, not per payment.

There is no API route to release a WITHHELD payment. If you believe a verdict is wrong, quote its amlResultId when you raise it.

Once a payment reaches SWEPT, the funds show up under balances, and you withdraw them from there. You can also sweep a static address yourself with POST /api/v1/wallets/{walletId}/sweep, passing the static wallet's id as walletId — for example, to retry at once rather than wait. If an automatic sweep is already running, that call returns it rather than starting a second one.