---
title: "Create reusable crypto deposit addresses for customers"
description: "Connect static crypto addresses to customer balance top-ups. Store the customer mapping, verify deposit webhooks, reconcile fees, and prevent duplicate credits."
---

# Create reusable crypto deposit addresses for customers

Use a reusable address when a customer needs to top up an account more than
once, without opening a new fixed-price checkout for every deposit. CoinsSend
static wallets provide a receiving address and deposit notifications; your
application owns the mapping between that address, the customer, and the
customer's balance.

For a specific order with an amount and an expiry, use the
[invoice checkout recipe](/solutions/accept-usdt-trc20-payments/).

## 1. Decide what a deposit credits

Before creating an address, choose whether your customer ledger is denominated
in cryptocurrency or a fiat unit. A USDT amount and a USD amount are distinct
fields even when their numerical values are close.

Keep these records in your application:

| Record | Purpose |
|---|---|
| Customer ID + coin + network | Identify whose top-up this address represents. |
| Returned address + QR URL | Display the correct receiving details again on the next visit. |
| Deposit transaction ID | Deduplicate notifications for one deposit. |
| Gross amount, net amount, fees, and currency | Explain the credit and reconcile it with the merchant balance. |
| Deposit and ledger states | Separate an observed deposit from funds available for spending or withdrawal. |

Choose a currently available pair using
[the public coin catalog](/payments/supported-coins/). Show both the asset and
the network to the customer.

## 2. Create and persist the static address

Send this body from your server to `POST /v1/wallet-address` with the signed
`Merchant` and `Sign` headers:

```json
{
  "network": "tron",
  "coin": "usdt",
  "type": "static",
  "label": "Customer 1042 top-ups",
  "webhook_url": "https://merchant.example/coinssend/deposits"
}
```

See [Authentication](/payments/guides/authentication/) for body serialization
and signing. Complete signed examples are available in
[PHP](/payments/examples/php-static-wallet/),
[Node.js](/payments/examples/js-static-wallet/), and
[Python](/payments/examples/python-static-wallet/).

The response contains `data.address` and `data.qr_code_url`. Store them with
your customer mapping before displaying them. The QR URL contains a wallet
record ID, not the blockchain address.

`label` is a human-readable annotation, not a unique customer identifier
enforced by CoinsSend. Multiple static addresses can exist for the same pair.
Serve the address you already stored when the customer opens the top-up screen
again.

Address creation has no request idempotency-key contract. If a create request
times out, reconcile it through merchant tooling or support before retrying:
another request can create another address.

## 3. Process deposits without crediting twice

The successful-deposit event is `wallet.transaction`. Verify `X-Signature`
against the exact raw body before trusting its contents.

After verification:

1. Match `data.wallet.address`, `data.wallet.coin`, and
   `data.wallet.network` to your stored customer mapping.
2. Use `data.transaction.id` as a stable business identifier for the deposit.
3. Store the signed gross, net, fee, and status fields.
4. Insert the deposit and its ledger entry atomically, with a uniqueness
   constraint that prevents the same deposit from crediting the customer twice.
5. Acknowledge a duplicate that has already been recorded successfully.

The signed payload supplies
`data.transaction.amounts.gross` and `data.transaction.amounts.net`, each with
fiat and crypto values. Base a net-credit policy on the appropriate signed net
field; do not subtract fees from an already net amount.

The callback uses the address-specific `webhook_url` when provided, otherwise
the merchant's default callback. Handle
`aml.rejected.static_wallet` as a separate rejection event rather than a
successful credit. The [webhook reference](/payments/guides/webhooks/) defines
the exact schemas and delivery rules.

## 4. Account for fees and availability

For `type: "static"`, deposits incur the configured merchant and network fees,
and the merchant's credited funds have a **24-hour hold**. Receipt of a deposit
event is not a promise that those funds are immediately withdrawable.

Your customer spending policy must account for that hold. Do not display
“available to withdraw” solely because a successful deposit webhook arrived.
Use [merchant balances](/payments/guides/merchants/) to reconcile available
funds and [merchant fees](/payments/guides/merchants/#get-merchant-fees) for
the current merchant-specific settings.

`type: "merchant"` has different crediting semantics and permits only one
wallet per merchant and pair. It is not an interchangeable setting for
customer-specific static addresses.

## Common integration mistakes

| Mistake | What to do instead |
|---|---|
| Creating an address every time the screen loads | Persist and reuse the customer's address mapping. |
| Matching a deposit using the label alone | Match the stored address and coin/network pair. |
| Crediting the amount on every callback | Deduplicate and update the ledger atomically. |
| Crediting USD as though it were the token amount | Use the explicit currency and crypto fields. |
| Treating a deposit as immediately withdrawable | Model the static-wallet hold and available balance separately. |

## Continue the integration

- [Static wallet contract and errors](/payments/guides/static-wallets/)
- [Webhook signature verification](/payments/guides/webhooks/)
- [Send a payout from your merchant balance](/solutions/usdt-usdc-payouts/)
- [Static wallets versus invoices](https://coinssend.com/blog/post/static-wallets-vs-invoices-for-crypto-payments/)
- [Open your merchant dashboard](https://app.coinssend.com/)
