Wallet Payout
Pay PIX from a user's USDT/USDC balance on Polygon or Base in a single API call.
Overview
/api/wallet/payout triggers a PIX payout funded by the user's USDT (Polygon), USDC (Base), or USDT-TRC20 (Tron) balance. You make one POST and one GET — Hodle does the rest.
You must call
POST /api/wallet/keysfirst to get the user'sprotectedSymmetricKey. Cache it on your side — fetch only once per user.Gated by feature flag. All three endpoints require the
WALLET_PAYOUT_APIper-user feature flag. Without it the endpoints respond403. Contact Hodle to enable it.
Flow
POST /api/wallet/keys— once per user. Cache the response.POST /api/wallet/payout— every payout. Returns202immediately with atransactionId.GET /api/wallet/payout/{transactionId}— poll every 5s untilCOMPLETEDorFAILED.
A typical run reaches COMPLETED in 30–90 seconds.
POST /api/wallet/payout
Request
curl --request POST \
--url https://api.hodle.com.br/api/wallet/payout \
--header "Authorization: Bearer $API_KEY" \
--header "Content-Type: application/json" \
--data '{
"value": 5000,
"network": "polygon",
"pixKey": "recipient@example.com",
"pixKeyType": "EMAIL",
"walletPin": "1234",
"protectedSymmetricKey": "AoofiKHyVRLvdrknnXzo..."
}'const res = await fetch('https://api.hodle.com.br/api/wallet/payout', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
value: 5000,
network: 'polygon',
pixKey: 'recipient@example.com',
pixKeyType: 'EMAIL',
walletPin: '1234',
protectedSymmetricKey: 'AoofiKHyVRLvdrknnXzo...',
}),
})
const data = await res.json()import os, requests
res = requests.post(
"https://api.hodle.com.br/api/wallet/payout",
headers={
"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"value": 5000,
"network": "polygon",
"pixKey": "recipient@example.com",
"pixKeyType": "EMAIL",
"walletPin": "1234",
"protectedSymmetricKey": "AoofiKHyVRLvdrknnXzo...",
},
)
data = res.json()Parameters
| Field | Type | Required | Description |
|---|---|---|---|
value | integer | Yes | Amount in BRL cents. Min 10 (R$ 0.10), max 100000 (R$ 1000). |
network | string | Yes | polygon (USDT-ERC20), base (USDC), or tron (USDT-TRC20). |
pixKey | string | Yes (or qrCode) | PIX key of the recipient. Mutually exclusive with qrCode. |
pixKeyType | string | Yes (when using pixKey) | One of PHONE, CPF, EMAIL, RANDOM, CNPJ. |
qrCode | string | Yes (or pixKey) | PIX BR Code copy-paste. Mutually exclusive with pixKey. |
walletPin | string | Yes | The PIN typed by the end user. |
protectedSymmetricKey | string | Yes | The value returned by POST /api/wallet/keys. |
Response
{
"success": true,
"transactionId": "65f1a8...",
"status": "PROCESSING",
"stableAmount": "9.31",
"valueInBrl": "50.00",
"fee": "1.55",
"network": "polygon",
"jobId": "1"
}The on-chain transfer is not yet final at this point. Use the GET endpoint to wait for confirmation.
Fees
fee = R$ 1.50 fixed + 2.5% of value
For value = 5000 (R$ 50.00), fee = R$ 2.75.
Errors
{ "success": false, "error": "WALLET_PAYOUT_API feature flag is not enabled for this user" }{ "success": false, "error": "Invalid PIN" }{ "success": false, "error": "Too many invalid PIN attempts. Locked for 24h." }{ "success": false, "error": "Failed to prepare full UserOp: ... ERC20: transfer amount exceeds balance ..." }{
"success": false,
"error": "Validation failed",
"details": [{ "field": "pixKeyType", "message": "pixKeyType is required when pixKey is provided" }]
}{ "success": false, "error": "Wait 28 seconds before retrying" }{ "success": false, "error": "PIX payments are temporarily disabled" }GET /api/wallet/payout/{transactionId}
Poll for the final state.
Request
curl --request GET \
--url "https://api.hodle.com.br/api/wallet/payout/65f1a8..." \
--header "Authorization: Bearer $API_KEY"const res = await fetch(
`https://api.hodle.com.br/api/wallet/payout/${transactionId}`,
{ headers: { Authorization: `Bearer ${process.env.HODLE_API_KEY}` } },
)
const data = await res.json()import os, requests
res = requests.get(
f"https://api.hodle.com.br/api/wallet/payout/{transaction_id}",
headers={"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}"},
)
data = res.json()Response
{
"success": true,
"data": {
"transactionId": "65f1a8...",
"status": "PENDING",
"network": "polygon",
"txHash": null,
"endToEndId": null,
"valueInBrl": "50.00",
"fee": "1.55",
"pixKey": "recipient@example.com",
"qrCode": null,
"provider": "WOOVI",
"correlationID": null,
"failureReason": null,
"createdAt": "2026-04-28T23:32:10.000Z",
"updatedAt": "2026-04-28T23:32:10.000Z"
}
}{
"success": true,
"data": {
"transactionId": "65f1a8...",
"status": "COMPLETED",
"network": "polygon",
"txHash": "0xeafe9c4985963a7a7d6e49f763cca5c6006693031402d46c0da2fced4519fe03",
"endToEndId": "E12345678202604281432abcdef123456",
"valueInBrl": "50.00",
"fee": "1.55",
"provider": "WOOVI",
"correlationID": "c8d3...",
"failureReason": null,
"createdAt": "2026-04-28T23:32:10.000Z",
"updatedAt": "2026-04-28T23:33:42.000Z"
}
}{
"success": true,
"data": {
"transactionId": "65f1a8...",
"status": "FAILED",
"txHash": "0xeafe9c...",
"failureReason": "...",
"...": "..."
}
}Status values
| Status | Meaning |
|---|---|
PENDING | Payout in progress. Keep polling. |
COMPLETED | On-chain transfer mined and PIX confirmed. |
FAILED | Something went wrong. failureReason explains what. |
REFUNDED | Reserved (not produced by this endpoint today). |
Funding requirements
The user's wallet must have enough USDT/USDC to cover value + fee. If insufficient, the call returns 400 immediately — no funds are moved.
Gas is sponsored by Hodle. The user does not need MATIC or ETH.