Deposit Asset

Create a deposit via Lightning, USDT, USDC, USDCE, BRLA, or BRS.

POST /api/deposit/asset

Create a deposit that converts BRL to the specified crypto asset. For BRS, the asset is minted to the user's own Hodle Solana wallet; the other assets are sent to the provided address.

Request

curl --request POST \
  --url https://api.hodle.com.br/api/deposit/asset \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "value": 5000,
    "address": "lnbc500u1pj...",
    "asset": "LIGHTNING",
    "externalId": "my-order-123"
  }'
const res = await fetch('https://api.hodle.com.br/api/deposit/asset', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: 5000,
    address: 'lnbc500u1pj...',
    asset: 'LIGHTNING',
    externalId: 'my-order-123',
  }),
})
const data = await res.json()
import os, requests

res = requests.post(
    "https://api.hodle.com.br/api/deposit/asset",
    headers={
        "Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "value": 5000,
        "address": "lnbc500u1pj...",
        "asset": "LIGHTNING",
        "externalId": "my-order-123",
    },
)
data = res.json()

Body example

POST /api/deposit/asset
{
  "value": 5000,
  "address": "lnbc500u1pj...",
  "asset": "LIGHTNING",
  "externalId": "my-order-123"
}

Parameters

FieldTypeRequiredDescription
valueintegerYesAmount in BRL cents. Must be a positive integer.
addressstringCond.Destination address. Required for LIGHTNING, USDT, USDC, USDCE, and BRLA; omit for BRS, which is minted to the account's own Hodle Solana wallet.
assetstringYesAsset type: LIGHTNING, USDT, USDC, USDCE, BRLA, BRS, DEPIX, or BTC.
networkstringCond.On-chain network for the asset. Required for USDT, USDC, USDCE, and BRLA; BRS only supports solana and defaults to it when omitted. Not needed for LIGHTNING. See Assets & Networks.
externalIdstringNoYour own ID for reconciliation. Must be unique per deposit (idempotency key). A UUID is generated if not provided.
subAccountIdstringNoThe subaccount this deposit belongs to (scoped to your API key — an id you do not own is refused with 404). Defaults to the main account. What it does depends on how your account is set up — see What subAccountId does.
taxIdstringNoCPF/CNPJ of the payer, when the payer is not the account holder. Digits only or masked. Rejected with 403 when third-party operations are not enabled for the account. On its own it does not let a third party pay the charge — see Who can pay the PIX.

What subAccountId does

What the field selects depends on how your integration is set up. There are two setups, and Hodle configures which one your account is on:

Your customers ramp through the partner. The deposit is settled on that subaccount's own account at the regulated partner, under their KYC. This is why an asset/network the partner does not cover is refused here rather than quietly falling back to your own rail — settling a customer's money on your balance is not the same operation.

Your customers are onboarded through Hodle's partner KYC, but your ramp settles elsewhere. Your subaccounts have no rail of their own to receive a deposit, so the field does not select one. The deposit runs on your own account — your rails, limits, fees and KYC, with every asset you normally support — and the id is recorded on the charge, so you can still tell which of your customers each deposit came from.

Ownership is checked either way: a subaccount id you do not own is refused with 404. The same setup governs POST /api/wallet/payout. Ask support if you are not sure which one your account is on.

Who can pay the PIX

The PIX charge returned by this endpoint can only be paid by the CPF/CNPJ that owns the account the deposit belongs to — the subaccount holder when subAccountId selects the rail, your own account otherwise. A PIX sent by anyone else is refused by the bank at payment time and no crypto is delivered.

To accept a PIX paid by a third party — for example when you charge your own end customer on your account — ask support to enable DEPOSIT_THIRD_PARTY on the account.

Assets & Networks

AssetNetworksAddress format
LIGHTNINGlightningLightning invoice or LNURL email
USDTpolygon, arbitrumEVM address (0x...)
USDCpolygon, base, gnosisEVM address (0x...)
USDCEgnosisEVM address (0x...)
BRLApolygon, baseEVM address (0x...)
BRSsolanaOwn Hodle Solana wallet
DEPIXspark, liquidSpark address (spark1...) or Liquid address
BTCsparkSpark address (spark1...)

Available networks per asset can depend on your account configuration and KYC level. For the full matrix across every endpoint see Assets & Networks.

BRS requires the NORA_RAIL flag and is always minted to the user's own Hodle Solana wallet. It does not accept a third-party destination. See BRS.

Address Formats

For LIGHTNING assets, the address can be:

  • A Lightning invoice starting with lnbc, lntb, or lnbcrt
  • An LNURL email in the format user@domain.com

For USDT, USDC, USDCE, and BRLA assets, the address is an EVM address (0x...) on the selected network.

For BRS, omit address; the asset is minted to the account's own Hodle Solana wallet.

Response

200 OK
{
  "success": true,
  "externalId": "my-order-123",
  "qrCode": "lnbc500u1pj...",
  "fee": 100,
  "fxRateAtTx": 408000.50,
  "walletCharge": "charge_abc123"
}

Fields

FieldTypeDescription
successbooleanWhether the deposit was created.
externalIdstringThe external ID for reconciliation (your value or auto-generated UUID).
qrCodestring | nullQR code for the deposit.
feenumberFee charged for the deposit.
fxRateAtTxnumberBTC/BRL exchange rate at the time of transaction.
walletChargestring | nullWallet charge identifier.

Errors

400 Bad Request
{
  "success": false,
  "error": "Validation failed",
  "details": [
    { "field": "value", "message": "Expected number, received string" }
  ]
}
409 Conflict
{
  "success": false,
  "error": "A deposit with this externalId already exists"
}

GET /api/deposit/asset/{externalId}

Reads a deposit back: whether the PIX was paid, whether the asset was delivered, and — on your own virtual account — who paid the QR code.

Poll it, or use it to reconcile after a DEPOSIT_ASSET_SUCCESS webhook.

Request

curl --request GET \
  --url https://api.hodle.com.br/api/deposit/asset/my-order-123 \
  --header "Authorization: Bearer $API_KEY"
const res = await fetch(
  'https://api.hodle.com.br/api/deposit/asset/my-order-123',
  { headers: { Authorization: `Bearer ${process.env.HODLE_API_KEY}` } },
)
const data = await res.json()
import os, requests

res = requests.get(
    "https://api.hodle.com.br/api/deposit/asset/my-order-123",
    headers={"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}"},
)
data = res.json()

Parameters

FieldInRequiredDescription
externalIdpathYesThe externalId of the deposit — your own value, or the UUID we generated and returned on the POST.
subAccountIdqueryCond.Required when the deposit was created with subAccountId. Send the same id back; an id you do not own is refused with 404.

Response

200 OK
{
  "success": true,
  "data": {
    "externalId": "my-order-123",
    "walletCharge": "68b1f0c2a4d3e50011aa22bb",
    "trackId": "trk_abc123",
    "status": "COMPLETED",
    "processingStep": null,
    "asset": "USDT",
    "network": "polygon",
    "address": "0x000000000000000000000000000000000000dEaD",
    "value": 5000,
    "valueInBrl": "50.00",
    "fee": 100,
    "feeInBrl": "1.00",
    "fxRateAtTx": 5.55,
    "receivedAmount": 8820000,
    "receivedAmountDecimals": 6,
    "transactionHash": "0xabc...",
    "qrCode": "00020126...",
    "endToEndId": "E1823612020260830120000000000001",
    "paidAt": "2026-08-30T12:00:00.000Z",
    "payer": {
      "name": "FULANO DE TAL",
      "taxId": "12345678901"
    },
    "confirmedAt": "2026-08-30T12:00:31.000Z",
    "createdAt": "2026-08-30T11:59:02.000Z",
    "updatedAt": "2026-08-30T12:00:31.000Z"
  }
}

Fields

FieldTypeDescription
externalIdstringYour reconciliation key.
walletChargestringHodle id of the charge, the same one the POST returned.
trackIdstring | nullHodle tracking id of the operation.
statusstringPENDING, FIAT_PAID, PROCESSING, COMPLETED, FAILED, REFUNDED or EXPIRED. See below.
processingStepstring | nullWhere a PROCESSING deposit currently is. Report it, never route on it.
assetstringAsset being delivered.
networkstringNetwork the asset is delivered on.
addressstringDestination address of the delivery.
valueintegerAmount in BRL cents.
valueInBrlstringSame amount, decimal string.
feeintegerFee in BRL cents.
feeInBrlstringSame fee, decimal string.
fxRateAtTxnumber | nullExchange rate at the time of the transaction.
receivedAmountinteger | nullAmount actually delivered, in the asset's smallest unit.
receivedAmountDecimalsinteger | nullDecimals of receivedAmount.
transactionHashstring | nullOn-chain tx hash of the delivery, when there is one.
qrCodestring | nullThe PIX QR code / BR Code we handed you on creation.
endToEndIdstring | nullEnd-to-end id of the PIX that funded the deposit. null until it is paid.
paidAtstring | nullISO-8601 timestamp of when the PIX settled.
payerobject | nullWho paid the PIX — see Who paid the QR code.
confirmedAtstring | nullISO-8601 timestamp of the delivery.
createdAtstringISO-8601 creation timestamp.
updatedAtstringISO-8601 timestamp of the last change.

Status

StatusMeaning
PENDINGCharge created, PIX not paid yet.
FIAT_PAIDFiat payment received; the asset has not yet been delivered. paidAt and endToEndId are available. Keep polling.
PROCESSINGFiat payment received, asset delivery in progress. Keep polling.
COMPLETEDAsset delivered. transactionHash is set when the rail produces one.
FAILEDThe deposit could not be delivered.
EXPIREDThe charge expired without being paid.

Who paid the QR code

payer carries the identity of whoever actually paid the PIX, as the payer's bank reports it.

FieldTypeDescription
namestring | nullName of the payer.
taxIdstring | nullCPF/CNPJ of the payer, digits only.

It is null when the PIX has not been paid yet, when the deposit was funded through a rail that carries no payer, or when your account does not own a virtual account. Only on your own virtual account is the payer your own customer; on the shared Hodle account they are our counterparty and stay private. Ask support to enable it if you need it.

Errors

404 Not Found
{
  "success": false,
  "error": "Deposit not found"
}

A deposit that belongs to another API key answers 404, never 403, so the status code cannot be used to probe for someone else's externalId.