Every stablecoin wallet in Tsara can have one or more blockchain deposit addresses associated with it.
These addresses allow you (or your customers) to send and receive USDC on the Solana network directly into your wallet.
Overview
- Generate unique deposit addresses for a wallet
- Retrieve and list existing addresses
- Receive on-chain payments directly to those addresses
- Automatically get notified via the
stablecoin.receivedwebhook when funds arrive - Label addresses for easy tracking and organization
- Generate unlimited addresses per wallet
Key Benefits of Multiple Addresses
| Use Case | Benefit |
|---|---|
| Per-User Addressing | Generate unique address for each customer for easy reconciliation |
| Payment Tracking | Create address per order/invoice for automatic matching |
| Campaign Tracking | Separate addresses for different marketing campaigns |
| Security | Rotate addresses periodically to enhance privacy |
| Reconciliation | Automated payment matching without manual intervention |
Create an Address
Generate a new Solana USDC-compatible address linked to a specific wallet.
Endpoint
POST /wallets/addresses
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer YOUR_SECRET_KEY | Yes |
Content-Type | application/json | Yes |
Request Parameters
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
wallet_id | string | Yes | Wallet ID to generate address for (format: wal_* or uid_*) | "wal_123" |
network | string | Yes | Blockchain network. Must be "solana" | "solana" |
label | string | No | Human-readable label for this address (max 100 chars) | "Customer #123 Deposit" |
metadata | object | No | Custom tracking data (max 10 keys, 500 chars per value) | {"customer_id": "cus_123"} |
Important: Address Limits
- No hard limit on addresses per wallet
- Recommended: Keep addresses under 1000 per wallet for optimal performance
- Addresses are permanent and cannot be deleted
- Each address can receive unlimited deposits
Request Example
{
"wallet_id": "wal_695fc9b9d4e992",
"network": "solana",
"label": "Customer #123 - Main Deposit",
"metadata": {
"customer_id": "cus_123",
"customer_email": "[email protected]",
"purpose": "deposits"
}
}curl -X POST "https://sandbox.tsara.ng/v1/wallets/addresses" \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "wal_695fc9b9d4e992",
"network": "solana",
"label": "Customer #123 - Main Deposit",
"metadata": {
"customer_id": "cus_123"
}
}'Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Address created successfully",
"data": {
"id": "addr_456",
"uid": "addr_456",
"wallet_id": "wal_695fc9b9d4e992",
"wallet_reference": "user_123_main_wallet",
"address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
"network": "solana",
"asset": "USDC",
"label": "Customer #123 - Main Deposit",
"status": "active",
"balance": 0,
"total_received": 0,
"deposit_count": 0,
"metadata": {
"customer_id": "cus_123",
"customer_email": "[email protected]",
"purpose": "deposits"
},
"created_at": "2025-01-31T12:00:00Z",
"last_deposit_at": null
},
"request_id": "req_1738318449"
}Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Request success status |
status | string | Request status text |
status_code | number | HTTP status code |
message | string | Human-readable message |
data.id | string | Address ID (format: addr_*) |
data.wallet_id | string | Parent wallet ID |
data.wallet_reference | string | Parent wallet reference |
data.address | string | Solana blockchain address (base58 encoded, 32-44 chars) |
data.network | string | Blockchain network (solana) |
data.asset | string | Asset this address receives (USDC) |
data.label | string | Address label |
data.status | string | Address status: active, inactive |
data.balance | number | Current balance on this specific address |
data.total_received | number | Total USDC received on this address (lifetime) |
data.deposit_count | number | Number of deposits received on this address |
data.metadata | object | Custom metadata |
data.created_at | string | Creation timestamp (ISO 8601) |
data.last_deposit_at | string | Last deposit timestamp (null if no deposits yet) |
request_id | string | Unique request identifier |
Address Status Values
| Status | Description |
|---|---|
active | Address is operational and can receive deposits |
inactive | Address monitoring paused (deposits still arrive but no webhooks sent) |
💡 The generated address can be shared with your users or used programmatically for deposits.
Address Format
Solana addresses are:
- Base58 encoded strings
- 32-44 characters long
- Example:
F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh - Case-sensitive
- Support both mainnet and devnet
Error Responses
{
"success": false,
"status_code": 400,
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "wallet_id",
"message": "Wallet not found"
}
]
}
}Common Errors
| Status Code | Error Code | Description |
|---|---|---|
| 400 | validation_error | Invalid request parameters |
| 400 | wallet_not_found | Wallet ID does not exist |
| 400 | invalid_network | Network not supported (use solana) |
| 401 | unauthorized | Invalid or missing API key |
| 429 | rate_limit_exceeded | Too many addresses created in short time |
Retrieve an Address
Get address details by address ID or blockchain address.
Endpoint
GET /wallets/addresses?id={address_id}
or
GET /wallets/addresses?address={blockchain_address}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | No* | Address ID (format: addr_*) |
address | string | No* | Blockchain address (Solana public key) |
*At least one parameter required
Example Request
curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?id=addr_456" \
-H "Authorization: Bearer YOUR_SECRET_KEY"or
curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?address=F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh" \
-H "Authorization: Bearer YOUR_SECRET_KEY"Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Address retrieved",
"data": {
"id": "addr_456",
"wallet_id": "wal_695fc9b9d4e992",
"wallet_reference": "user_123_main_wallet",
"address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
"network": "solana",
"asset": "USDC",
"label": "Customer #123 - Main Deposit",
"status": "active",
"balance": 150.50,
"total_received": 500.00,
"deposit_count": 5,
"metadata": {
"customer_id": "cus_123"
},
"created_at": "2025-01-31T12:00:00Z",
"last_deposit_at": "2025-01-31T15:30:00Z"
}
}List All Addresses
Retrieve all addresses for a wallet with pagination and filtering.
Endpoint
GET /wallets/addresses?page={page}&limit={limit}
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | number | No | 1 | Page number (starts at 1) |
limit | number | No | 20 | Items per page (max: 100) |
wallet_id | string | No | - | Filter by wallet ID |
wallet_reference | string | No | - | Filter by wallet reference |
status | string | No | - | Filter by status: active, inactive |
has_balance | boolean | No | - | Filter addresses with balance > 0 |
Example Request
curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?wallet_reference=user_123_main_wallet&page=1&limit=20" \
-H "Authorization: Bearer YOUR_SECRET_KEY"Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Addresses retrieved",
"data": [
{
"id": "addr_001",
"wallet_id": "wal_695fc9b9d4e992",
"address": "9szQnP5LwYmX4rN8tV6uKdJ2fE3gH7aB1cD5eF9gH0iJ",
"label": "Main Deposit",
"network": "solana",
"asset": "USDC",
"status": "active",
"balance": 100.00,
"total_received": 250.00,
"deposit_count": 3,
"created_at": "2025-01-30T10:00:00Z",
"last_deposit_at": "2025-01-31T14:00:00Z"
},
{
"id": "addr_002",
"wallet_id": "wal_695fc9b9d4e992",
"address": "8mxLt2pK9vN6wM5uJ4hG3fD1eC0bA8iH7gF6eD5cB4aZ",
"label": "Promo Campaign",
"network": "solana",
"asset": "USDC",
"status": "active",
"balance": 50.50,
"total_received": 150.00,
"deposit_count": 2,
"created_at": "2025-01-31T08:00:00Z",
"last_deposit_at": "2025-01-31T12:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"total_pages": 1,
"has_more": false
},
"summary": {
"total_addresses": 2,
"active_addresses": 2,
"total_balance": 150.50,
"total_received": 400.00
},
"request_id": "req_xyz"
}List Response Fields
| Field | Type | Description |
|---|---|---|
summary.total_addresses | number | Total number of addresses |
summary.active_addresses | number | Number of active addresses |
summary.total_balance | number | Combined balance across all addresses |
summary.total_received | number | Total USDC received across all addresses (lifetime) |
Update Address (Optional)
Update address label or metadata. Address itself cannot be changed.
Endpoint
PATCH /wallets/addresses/{address_id}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
label | string | No | New label for address |
metadata | object | No | Updated metadata (replaces existing) |
status | string | No | Update status: active or inactive |
Example Request
curl -X PATCH "https://sandbox.tsara.ng/v1/wallets/addresses/addr_456" \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "Customer #123 - Updated Label",
"metadata": {
"customer_id": "cus_123",
"tier": "premium"
}
}'Response
{
"success": true,
"message": "Address updated",
"data": {
"id": "addr_456",
"label": "Customer #123 - Updated Label",
"metadata": {
"customer_id": "cus_123",
"tier": "premium"
},
"updated_at": "2025-01-31T16:00:00Z"
}
}Webhook Notifications
Whenever USDC is received on an address, Tsara automatically sends a webhook event.
Event Type: stablecoin.received
Example Webhook Payload
{
"id": "evt_789",
"type": "stablecoin.received",
"created_at": "2025-01-31T15:30:00Z",
"data": {
"wallet_id": "wal_695fc9b9d4e992",
"wallet_reference": "user_123_main_wallet",
"address_id": "addr_456",
"address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
"amount": 50.00,
"asset": "USDC",
"network": "solana",
"transaction_hash": "5quKxN7pL9mW8vR3tY6uJ2fE4gH1aB0cD5eF9gH3iJkL",
"explorer_url": "https://explorer.solana.com/tx/5quKxN7pL9mW8vR3tY6uJ2fE4gH1aB0cD5eF9gH3iJkL",
"sender": "CfuQhBTFXtHDVDyWpAjhQXmuN1PRLtAne5sppz9D19d",
"balance_before": 100.50,
"balance_after": 150.50,
"metadata": {
"customer_id": "cus_123"
},
"confirmed_at": "2025-01-31T15:30:00Z"
}
}Webhook Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID |
type | string | Event type (stablecoin.received) |
created_at | string | Event timestamp |
data.address_id | string | Address ID that received funds |
data.amount | number | USDC amount received |
data.transaction_hash | string | Solana transaction hash |
data.sender | string | Sender's Solana address |
data.balance_before | number | Address balance before deposit |
data.balance_after | number | Address balance after deposit |
data.metadata | object | Address metadata (useful for automatic reconciliation) |
Using Webhooks for Reconciliation
app.post('/webhooks/tsara', (req, res) => {
const event = req.body;
if (event.type === 'stablecoin.received') {
const deposit = event.data;
const customerId = deposit.metadata.customer_id;
await creditCustomerAccount(customerId, deposit.amount);
await db.deposits.create({
customer_id: customerId,
amount: deposit.amount,
address: deposit.address,
transaction_hash: deposit.transaction_hash,
status: 'completed'
});
await notifyCustomer(customerId, `Received ${deposit.amount} USDC`);
}
res.sendStatus(200);
});See Webhooks Security for signature verification.
Use Cases & Examples
Per-Customer Deposit Addresses
Generate unique address for each customer for automatic payment matching.
async function createCustomerDepositAddress(customerId, customerEmail) {
const wallet = await getBusinessWallet();
const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_id: wallet.id,
network: 'solana',
label: `Customer ${customerId} Deposit`,
metadata: {
customer_id: customerId,
customer_email: customerEmail,
created_at: new Date().toISOString()
}
})
});
const data = await response.json();
await db.customers.update(customerId, {
deposit_address_id: data.data.id,
deposit_address: data.data.address
});
return data.data.address;
}Order-Specific Payment Addresses
Generate unique address per order for automatic reconciliation.
async function generateOrderPaymentAddress(orderId, orderAmount) {
const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_id: BUSINESS_WALLET_ID,
network: 'solana',
label: `Order #${orderId}`,
metadata: {
order_id: orderId,
expected_amount: orderAmount,
created_for: 'order_payment'
}
})
});
const data = await response.json();
await db.orders.update(orderId, {
payment_address: data.data.address,
expected_amount: orderAmount,
status: 'awaiting_payment'
});
return data.data.address;
}Campaign Tracking
Create addresses for different marketing campaigns.
async function createCampaignAddress(campaignName) {
const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_id: MARKETING_WALLET_ID,
network: 'solana',
label: `Campaign: ${campaignName}`,
metadata: {
campaign_name: campaignName,
created_at: new Date().toISOString()
}
})
});
return response.json();
}Balance Monitoring per Address
Monitor specific addresses for deposits.
async function monitorAddressDeposits(addressId) {
const response = await fetch(
`https://sandbox.tsara.ng/v1/wallets/addresses?id=${addressId}`,
{
headers: {
'Authorization': `Bearer ${SECRET_KEY}`
}
}
);
const data = await response.json();
const address = data.data;
console.log(`Address: ${address.address}`);
console.log(`Balance: ${address.balance} USDC`);
console.log(`Total Received: ${address.total_received} USDC`);
console.log(`Deposits: ${address.deposit_count}`);
return address;
}Address Rotation for Privacy
Periodically create new addresses for enhanced privacy.
async function rotateCustomerAddress(customerId) {
const currentAddress = await getCustomerAddress(customerId);
await updateAddressStatus(currentAddress.id, 'inactive');
const newAddress = await createCustomerDepositAddress(customerId);
await db.customers.update(customerId, {
previous_addresses: [
...currentAddress,
currentAddress.address
],
current_deposit_address: newAddress
});
await notifyCustomer(customerId, 'Please use new deposit address');
return newAddress;
}Tips & Best Practices
-
Use metadata for automatic reconciliation
metadata: { customer_id: customerId, order_id: orderId, expected_amount: amount }Webhook includes metadata for instant matching.
-
Label addresses clearly
label: `${customerName} - ${purpose} - ${date}`Makes debugging and support easier.
-
Store address associations
await db.addresses.create({ address_id: address.id, blockchain_address: address.address, customer_id: customerId, purpose: 'deposits', created_at: address.created_at }); -
Monitor address usage
const addresses = await listAddresses(wallet_id); const unusedAddresses = addresses.filter(a => a.deposit_count === 0); -
Handle duplicate deposits
app.post('/webhooks/tsara', async (req, res) => { const txHash = req.body.data.transaction_hash; if (await db.deposits.exists({ transaction_hash: txHash })) { return res.sendStatus(200); } await processDeposit(req.body.data); res.sendStatus(200); }); -
Display address as QR code
import QRCode from 'qrcode'; const qrCode = await QRCode.toDataURL(address.address); -
Validate address before sharing
function isValidSolanaAddress(address) { return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); } -
Track address performance
const performance = { address: address.address, conversion_rate: address.deposit_count / totalShares, average_deposit: address.total_received / address.deposit_count, last_active: address.last_deposit_at };
Troubleshooting
Address not receiving deposits
Cause: Wrong address shared or customer sending wrong asset.
Solution:
- Verify address on Solana Explorer
- Ensure customer is sending USDC (not SOL)
- Confirm address belongs to your wallet
- Check network (mainnet vs devnet)
Verification:
curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?address=YOUR_ADDRESS" \
-H "Authorization: Bearer YOUR_SECRET_KEY"Webhook not received after deposit
Cause: Webhook URL not configured or signature validation failing.
Solution:
- Configure webhook URL in dashboard
- Verify signature validation (HMAC-SHA512)
- Ensure endpoint returns 200 OK
- Check webhook logs in dashboard
Balance not updating
Cause: Blockchain confirmation pending.
Solution:
- Wait 30-60 seconds for confirmation
- Check transaction on Solana Explorer
- Verify transaction hash from sender
- Ensure USDC contract address is correct:
- Mainnet:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v - Devnet:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
- Mainnet:
Cannot find address after creation
Cause: Using wrong wallet reference or address ID.
Solution:
const address = await fetch(
`https://sandbox.tsara.ng/v1/wallets/addresses?wallet_reference=${walletRef}`,
{ headers: { 'Authorization': `Bearer ${SECRET_KEY}` } }
);Duplicate deposits recorded
Cause: Webhook retry or not checking transaction hash uniqueness.
Solution:
await db.deposits.findOrCreate({
where: { transaction_hash: deposit.transaction_hash },
defaults: {
customer_id: customerId,
amount: deposit.amount,
address: deposit.address
}
});Address shows inactive status
Cause: Address was manually deactivated or wallet suspended.
Solution:
- Check wallet status
- Reactivate address if needed
- Contact support if wallet suspended
Security Considerations
Address Validation
Always validate addresses before displaying to users:
function validateSolanaAddress(address) {
const errors = [];
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) {
errors.push('Invalid Solana address format');
}
if (address.length < 32 || address.length > 44) {
errors.push('Invalid address length');
}
return errors.length === 0;
}Prevent Address Reuse Across Customers
async function ensureUniqueAddress(customerId) {
const existingAddress = await db.addresses.findOne({
where: { customer_id: customerId, status: 'active' }
});
if (existingAddress) {
return existingAddress;
}
return await createCustomerDepositAddress(customerId);
}Monitor for Suspicious Activity
async function checkSuspiciousDeposit(deposit) {
const recentDeposits = await db.deposits.count({
where: {
address: deposit.address,
created_at: { $gte: Date.now() - 3600000 }
}
});
if (recentDeposits > 10) {
await alertSecurityTeam(`High frequency deposits: ${deposit.address}`);
}
if (deposit.amount > 10000) {
await alertSecurityTeam(`Large deposit: ${deposit.amount} USDC`);
}
}Related Pages
- Wallets — Create and manage stablecoin wallets
- Webhooks — Handle
stablecoin.receivedevents - Webhooks Security — Signature verification guide
- Checkout — Accept USDC payments via checkout
- Errors & Status Codes — Complete error reference