Direct payment returns
When TBMC must return a deposit, it chooses a destination on the same chain as that deposit. Set return wallets before funding an order so a canceled, expired, or overfunded payment can be returned to the right person. A return can also be needed for a deposit that arrives after the order has finished.
This guide explains how to set a user's default return wallets, register a destination for one payment order, and read the result. It assumes you have an API key and have registered the user and their wallets.
In the console, you can set an order's return wallets when you cancel it, or from the notice on a deposit that waits for a wallet.
How TBMC chooses a destination
For each direct payment deposit that needs a return, TBMC tries these options in order:
- The payment order's return wallet for the deposit's chain.
- The funding user's default return wallet for that chain, if the order has no return details.
TBMC never returns a deposit to its sending address automatically. If no destination is configured, TBMC holds the
return with RETURN_DETAILS_MISSING until you set one.
If the order has explicit details that cannot cover the deposit, it reports RETURN_DETAILS_INCOMPATIBLE. Explicit
details do not fall through to a user default.
Orders created through linked Receive rulesets require explicit order-level return details. They do not use user defaults.
TBMC records the chosen address before it sends a return. A later details update cannot change a recorded destination. Do not use a pooled or exchange address as a return wallet unless its operator can credit the correct end user.
Step 1 — Register and activate the return wallets
Register each address with POST /add-user-record. The return recipient must be a user in the same account as the
payment order. Newly registered wallets start PENDING. You can save them as user defaults in the same request, but
TBMC uses a default for returns only after its wallet becomes ACTIVE. Order-level return details require active
wallets.
Use GET /supported-assets for current asset and chain coverage. Return registration also checks that TBMC can return
funds on the selected chain. Provide one wallet per chain.
Step 2 — Set a user's default return wallets
Use this to specify default return wallets for payment orders funded by this user. Send returnWallets in a
POST /add-user-record request:
curl -X POST https://api.bettermoney.com/api/v1/add-user-record \
-H "x-api-key: $TBMC_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"label": "usr_3f8a92",
"wallets": [
{ "chain": "ethereum", "address": "0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2" }
],
"returnWallets": [
{ "chain": "ethereum", "address": "0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2" }
]
}'
const wallet = {
chain: 'ethereum',
address: '0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2',
};
const res = await fetch('https://api.bettermoney.com/api/v1/add-user-record', {
method: 'POST',
headers: {
'x-api-key': process.env.TBMC_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
label: 'usr_3f8a92',
wallets: [wallet],
returnWallets: [wallet],
}),
});
const { data: user } = await res.json();
When you change default return wallets, send the full list you want TBMC to save. TBMC removes any old default wallet
you leave out. If you do not want to change the defaults, leave returnWallets out of the request.
Step 3 — Read one payment order's return state
After creating the payment order, read it by ID:
curl "https://api.bettermoney.com/api/v1/payment-orders/$ORDER_ID" \
-H "x-api-key: $TBMC_API_KEY"
const orderId = '9f2c7b1a-4d3e-4a8b-bc21-1e2f3a4b5c6d';
const res = await fetch(`https://api.bettermoney.com/api/v1/payment-orders/${orderId}`, {
headers: { 'x-api-key': process.env.TBMC_API_KEY! },
});
const order = await res.json();
The single-order response contains a returns block. Before order-level registration, details is null and
revision is 0. This excerpt shows the block before any deposit needs a return:
{
"returns": {
"details": null,
"revision": 0,
"transfers": []
}
}
Its transfers array reports per-deposit return routing. Each entry is either:
| Status | Meaning |
|---|---|
AWAITING_DETAILS |
TBMC needs a usable destination. Read reason to see whether details are missing or incompatible. |
ROUTED |
TBMC has recorded destination.source and destination.address. That destination is fixed for this deposit. |
destination.source is details, user, or sender. Only older recorded returns report sender. Order lists and
webhook payloads do not contain the returns block; use the single-order GET when you need its revision or per-deposit
state.
Step 4 — Register return details for one order
Use this when an order needs a specific return recipient. See Payment Orders in the API Reference for
the endpoint schema. You can register before funding or after a deposit is observed. Read the order first and pass
returns.revision as If-Match. Use a new Idempotency-Key for each intended replacement, and keep that key when
retrying the same request.
curl -X PUT "https://api.bettermoney.com/api/v1/payment-orders/$ORDER_ID/return-details" \
-H "x-api-key: $TBMC_API_KEY" \
-H 'Content-Type: application/json' \
-H 'If-Match: "0"' \
-H 'Idempotency-Key: 8a7ec5b4-3bd8-467d-9475-508e0c1cb869' \
-d '{
"userId": "17e2467b-09d9-45f1-8112-21a967341129",
"wallets": [
{ "chain": "ethereum", "address": "0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2" }
]
}'
const orderId = '9f2c7b1a-4d3e-4a8b-bc21-1e2f3a4b5c6d';
const res = await fetch(`https://api.bettermoney.com/api/v1/payment-orders/${orderId}/return-details`, {
method: 'PUT',
headers: {
'x-api-key': process.env.TBMC_API_KEY!,
'Content-Type': 'application/json',
'If-Match': '"0"',
'Idempotency-Key': '8a7ec5b4-3bd8-467d-9475-508e0c1cb869',
},
body: JSON.stringify({
userId: '17e2467b-09d9-45f1-8112-21a967341129',
wallets: [{ chain: 'ethereum', address: '0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2' }],
}),
});
const returns = await res.json();
userId is the system ID returned by POST /add-user-record, not your user label. Include at least one wallet and no
more than 32, with one address per chain. Every address must already be an active wallet registered to that user. The
PUT replaces the entire order-level list; it does not add to the old list. There is no delete operation.
A successful response returns the saved details, the new revision, the current transfers, and affectedTransfers
(transaction hashes whose uncommitted return can use the replacement). Registration does not send a return immediately.
Read the order again to follow each transfer.
An accepted registration before funding returns 200 with this body:
{
"details": {
"userId": "17e2467b-09d9-45f1-8112-21a967341129",
"wallets": [{ "chain": "ethereum", "address": "0x4e3a1b2c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2" }],
"revision": 1,
"registeredAt": "2026-10-06T14:00:00.000Z",
"requestId": "8a7ec5b4-3bd8-467d-9475-508e0c1cb869"
},
"revision": 1,
"transfers": [],
"affectedTransfers": []
}
To replace the details, read the latest returns.revision, send the complete new wallet list, and use a new idempotency
key. TBMC validates every observed deposit whose return destination can still change. Include a wallet for each such
deposit's chain. Deposits with a recorded destination keep it and do not constrain the replacement. A later deposit on a
new chain may need another update.
Handle a refused registration
These errors apply to order-level registration. Every error response uses the same shape:
{ "error": "Return recipient not found" }
| HTTP status | What to do |
|---|---|
400 |
Fix malformed input, headers, or duplicate wallet chains. |
401 |
Send a valid API key in the x-api-key header. |
403 |
Use a customer API key for an ACTIVE account with approved KYB. |
404 |
Check that the order and return recipient belong to your account. |
409 |
Read the order again. Use its current revision for a new replacement. If a wallet activation or another order update is in progress, retry the same request shortly. A replacement also returns 409 when every observed transfer already has a committed destination. |
422 |
Read the error text for the affected chain and asset. Activate and register the wallet, choose a supported return chain, or add the missing chain to the complete wallet list. |
429 |
Wait for the Retry-After interval, then retry with the same idempotency key. |
500 |
Retry later with the same idempotency key. Contact TBMC if the error persists. |
503 |
Retry later with the same idempotency key; no new details were accepted. |
Retrying the same key with the same body and expected revision returns the recorded response, even if the order has
since advanced. Reusing the key for different details returns 409. A registration can succeed before its event
finishes processing; use the single-order GET to see the accepted state and any waiting transfer.
What's next
- Browse the API Reference under Payment Orders for the full request and response schemas.
- Use Webhooks to follow payment-order and deposit status changes.
- Start with Getting Started to create and fund your first payment order.