Stable ↔ PIX
End-to-end flow — go from API key to a settled PIX payout funded by USDT or USDC.
What this flow does
A user holds USDT (Polygon or Tron) or USDC (Base) in a Hodle-managed wallet. You trigger a PIX payment in BRL — Hodle covers gas, swaps stable → BRLA, and settles the recipient's PIX account. Total elapsed time is typically 30–90 seconds end-to-end.
Use this when:
- You're building a fintech that holds USD-pegged stables for users in Brazil.
- You want to pay PIX from a payroll, a marketplace, or a remittance flow without ever holding BRL.
- Recipients are anywhere in Brazil (any bank, any PIX key type).
Sequence
1. POST /api/user/create → userId
2. POST /api/wallet/create → wallet provisioned (Polygon/Base/Tron)
3. POST /api/kyc → submit KYC, wait for APPROVED
4. (off-chain) fund the wallet → on-ramp via /api/deposit/asset OR external transfer
5. POST /api/wallet/keys → cache protectedSymmetricKey ONCE per user
6. POST /api/wallet/payout → get transactionId
7. GET /api/wallet/payout/{id} → poll until COMPLETEDSteps 1–3 happen once per user. Steps 5–7 happen per payout — step 5 uses a cached value.
Step 1 — Create the user
curl -X POST https://api.hodle.com.br/api/user/create \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com", "name": "João da Silva" }'const { data } = await fetch('https://api.hodle.com.br/api/user/create', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: 'user@example.com', name: 'João da Silva' }),
}).then((r) => r.json())
const userId = data.userId{
"success": true,
"data": {
"userId": "65f1a83b6b7c2b001f3c9e21",
"email": "user@example.com",
"linkedToUserId": "65b00...",
"status": "pending_registration",
"createdAt": "2026-05-09T22:00:00.000Z"
}
}Step 2 — Provision a wallet
curl -X POST https://api.hodle.com.br/api/wallet/create \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "userId": "65f1a83b...", "network": "polygon" }'{
"success": true,
"data": {
"address": "0x4b1f...c9a2",
"network": "polygon",
"balance": "0"
}
}The address is the same on Polygon, Base, and any other EVM network — it's an ERC-4337 smart account.
Step 3 — KYC
Submit POST /api/kyc with the user's data and document photos. See the dedicated KYC guide for the full schema. Wait for status: APPROVED (webhook or poll).
A user who is not APPROVED will receive 403 from /api/wallet/payout.
Step 4 — Fund the wallet
Two options:
- On-ramp via Hodle:
POST /api/deposit/asset— converts BRL (PIX) into USDT/USDC and credits the wallet. - External transfer: Send USDT/USDC from any wallet to the address from step 2. Hodle indexes the inbound transfer and credits the user automatically.
Verify the funds landed with POST /api/wallet/get.
Step 5 — Cache the symmetric key
Call once and store the result alongside the user in your DB:
curl -X POST https://api.hodle.com.br/api/wallet/keys \
-H "Authorization: Bearer $API_KEY"{ "success": true, "data": { "protectedSymmetricKey": "Aoofi...", "email": "user@example.com" } }The key only changes if the user resets their PIN. See Wallet Keys.
Step 6 — Trigger the payout
curl -X POST https://api.hodle.com.br/api/wallet/payout \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"value": 5000,
"network": "polygon",
"pixKey": "recipient@example.com",
"pixKeyType": "EMAIL",
"walletPin": "1234",
"protectedSymmetricKey": "Aoofi..."
}'{
"success": true,
"transactionId": "65f1a8...",
"status": "PROCESSING",
"stableAmount": "9.31",
"valueInBrl": "50.00",
"fee": "1.55",
"network": "polygon"
}value is in BRL cents. Full schema and all error cases are in Wallet Payout.
Step 7 — Poll until settled
curl https://api.hodle.com.br/api/wallet/payout/65f1a8... \
-H "Authorization: Bearer $API_KEY"Poll every 5 seconds. Final states:
COMPLETED— PIX settled.endToEndIdis the bank's reference.FAILED—failureReasonexplains. The on-chain transfer is reverted; the user's stable balance is restored.
For server-to-server flows, prefer webhooks (payout.completed, payout.failed) over polling.
Failure handling
| Failure | What you see | What to do |
|---|---|---|
| Insufficient balance | 400 from /api/wallet/payout | Top up via step 4. No funds were touched. |
| Wrong PIN (3rd attempt) | 400 Too many invalid PIN attempts | User is locked for 24h. Surface in your UI. |
| KYC not approved | 403 on transactional endpoints | Re-submit KYC, wait for APPROVED. |
| Recipient bank refused PIX | payout.failed webhook, failureReason set | Retry with a different pixKey. |
Reconciliation
Use POST /api/account/statement to pull every operation in a window. Each payout row carries txHash (on-chain) and endToEndId (PIX), which are also in the bank's report — that's the join key for closing-the-books.