Platforms and marketplaces
Give every user a unique crypto deposit address
One address for everyone is the problem: money arrives and nothing tells you whose it was. Create a customer with your own user id and you get a permanent deposit address derived from your wallet — so every payment identifies itself, and none of it passes through us.
Why one shared address does not work
Every platform that accepts crypto hits the same wall in the same order, usually after the first support ticket about a payment nobody can find.
- A single address tells you an amount and a sender, never which of your users it belongs to. Matching by amount breaks the first time two people send the same figure.
- The usual workaround is to pool everything and pay out later — which turns a payments feature into holding other people’s money, the single heaviest thing a platform can take on.
- Asking users to paste a memo or reference works until someone forgets, and then it is a human reconciling a blockchain by hand.
One call per user, once
You send your own identifier for the user. You get back addresses that never change, so the call happens at signup and never again.
- 01
Create the customer with your own id
Pass your existing user id as `external_ref`. Unheld does not mint an identifier you then have to store and map — the record is keyed by the id your database already uses.
- 02
Store the addresses you get back
The response carries the deposit addresses for that user, derived from your own wallet. They are permanent, so you can display them, put them in a QR code, or let the user save them and send from anywhere.
- 03
Credit the account when the webhook arrives
A payment to that address is attributed to the customer it belongs to, and the notification carries your own `externalRef` back to you. Your application credits a user id it already knows, without matching amounts or reading a chain.
The actual call
Request
curl -X POST https://api.unheld.io/api/v1/customers \
-H "Authorization: Bearer $UNHELD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "user_8412",
"label": "Ada Lovelace",
"supported_assets": ["ETH", "USDC"],
"evm": true
}'Response
{
"id": "c7f1…",
"external_ref": "user_8412",
"label": "Ada Lovelace",
"evm_address": "0x…",
"supported_assets": ["ETH", "USDC"],
"receive_method": "auto"
}Requires an API key with the `customers:write` scope; reading them back needs `customers:read`. Addresses for every chain a user can pay on are available from `GET /api/v1/customers/:id/addresses`.
How a payment finds its user
The address is the identifier. Because it belongs to exactly one customer and never changes, an arriving payment is attributed without a memo, a reference field, or an amount lookup — the three things that break in production.
Invoices can also be linked to a customer with `customerId`, so a one-off charge and a standing deposit address attribute to the same user in your dashboard and your webhooks.
Where the money actually sits
Funds land on addresses derived from your own wallet. Unheld stores only the watch-only public part, so it cannot move, freeze or lose them, and there is no balance held here to be paid out to you later.
What that does not tell you is whether your platform is holding funds for your users. It probably is, if payments arrive on addresses you control and you owe your users the value. That is a question about your arrangement and your jurisdiction, and it is not one we can answer for you — take advice on it. We can only state our side: the money never passes through Unheld.
Designing on this
Every one of these is easier to design around now than to discover in production.
- Every user address derives from one wallet — yours. This gives you a single recovery phrase to protect, not one per user, and it means the funds are not segregated per user by keys.
- A permanent address is reused by design, which is what makes attribution work. It also means a user’s payment history to you is linkable on-chain by anyone who learns their address.
- A user’s address is fixed per chain family, so the same EVM address receives on every EVM network you have enabled. Enabling a new chain does not require re-issuing addresses.
- Deleting a customer stops the platform watching their address; it does not and cannot move anything already sent there. Money that arrives after deletion is still yours, but nothing will notify you about it.
Questions
Does Unheld hold my users’ funds?
No. Payments go to addresses derived from your own wallet and Unheld stores only the watch-only public key, so there is no balance here and nothing to withdraw. Whether your platform is itself holding value for your users is a separate question about your own arrangement, and one worth getting advice on rather than an answer from a marketing page.
How many users can have their own address?
There is no per-user address limit — addresses are derived, not allocated from a pool, so creating the millionth customer is the same operation as the first. What is metered is your plan’s customer allowance and transaction count, which are shown on the pricing page.
Can a user’s address ever change?
No. That is the property the whole design rests on: the address identifies the user, so it is issued once and stays. You can display it, embed it in a QR code, or let the user save it in their own wallet, and it will still be correct a year later.
What if two users pay the same amount at the same time?
Nothing ambiguous happens, because amounts are never used to identify anyone. Each payment arrives on an address belonging to exactly one customer, and the webhook carries your own reference for that customer — identical amounts, identical timing and identical senders are all irrelevant.
Create a customer on testnet
Make the call with a test API key, get real addresses back, and send a testnet payment to one of them to watch the attribution arrive — before any real money is involved.
Start freeDevelopers · Subscriptions · Pricing · Non-custodial · Answered plainly.