Wallet Payout
Pay PIX from a user's USDT, USDC, or BRLA balance on Polygon or Base in a single API call.
Overview
/api/wallet/payout triggers a PIX payout funded by the user's stablecoin balance on Polygon or Base. You make one POST and one GET — Hodle does the rest.
Pick the funding stablecoin with the asset field:
| Network | Supported assets | Default |
|---|---|---|
polygon | USDT, BRLA | USDT |
base | USDC, BRLA | USDC |
BRLA is a BRL-pegged stablecoin, so it converts 1:1 to the PIX amount (no FX). USDT/USDC are converted at the current BRL rate.
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()To fund the payout with BRLA instead, add "asset": "BRLA" (works on both polygon and base):
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": "base",
"asset": "BRLA",
"pixKey": "recipient@example.com",
"pixKeyType": "EMAIL",
"walletPin": "1234",
"protectedSymmetricKey": "AoofiKHyVRLvdrknnXzo..."
}'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 or base. |
asset | string | No | Funding stablecoin. polygon: USDT (default) or BRLA. base: USDC (default) or BRLA. |
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. |
subAccountId | string | No | Pay out from this subaccount's wallet (scoped to your API key). walletPin/protectedSymmetricKey must be the subaccount's. Defaults to the main account. |
Response
{
"success": true,
"transactionId": "65f1a8...",
"status": "PROCESSING",
"stableAmount": "9.31",
"valueInBrl": "50.00",
"fee": "1.55",
"network": "polygon",
"asset": "USDT",
"jobId": "1"
}stableAmount is the amount of asset debited from the user. For BRLA it equals value + fee in BRL (1:1); for USDT/USDC it is the BRL total converted at the current rate.
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": "USDT is not supported on base" }{ "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 of the chosen asset (USDT, USDC, or BRLA) 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.