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
chainTypeandreferencetogether. Asking again with the same pair returns the address that pair already has rather than minting a second one; the same reference with anotherchainTypegets that family's own address. The reference is trimmed before it is matched and stored. The call always answers201, 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
EVMaddress 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 achainTyperather than achainId.
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:
| 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.