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 COMPLETED

Steps 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
201 Created
{
  "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" }'
201 Created
{
  "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..."
  }'
202 Accepted
{
  "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. endToEndId is the bank's reference.
  • FAILEDfailureReason explains. 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

FailureWhat you seeWhat to do
Insufficient balance400 from /api/wallet/payoutTop up via step 4. No funds were touched.
Wrong PIN (3rd attempt)400 Too many invalid PIN attemptsUser is locked for 24h. Surface in your UI.
KYC not approved403 on transactional endpointsRe-submit KYC, wait for APPROVED.
Recipient bank refused PIXpayout.failed webhook, failureReason setRetry 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.