This guide walks through the full payout flow: create an intent, approve it, execute the transfer from your wallet, and let billing.io verify the settlement on-chain.
See Payouts for the conceptual overview.
Payout orchestration is available on Scale plans and above. Reconciliation
reports are available on the Enterprise plan.
Step 1: Create a Payout Intent
A payout intent declares your intention to send crypto to a recipient. It starts in draft status.
curl -X POST https://api.billing.io/v1/payouts \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"recipient_address": "TXrkA1b2C3d4E5f6G7h8I9j0K1l2M3n4",
"chain": "tron",
"token": "USDT",
"amount": 500.00,
"currency": "USD",
"description": "Contractor payment - June 2025",
"reference_type": "invoice",
"reference_id": "inv_a1b2c3d4",
"due_date": "2025-06-30T00:00:00Z"
}'
Response:
{
"id": "po_e5f6a7b8c9d0e1f2a3b4c5d6",
"org_id": "org_...",
"recipient_address": "TXrkA1b2C3d4E5f6G7h8I9j0K1l2M3n4",
"chain": "tron",
"token": "USDT",
"amount": 500.00,
"amount_atomic": null,
"currency": "USD",
"status": "draft",
"description": "Contractor payment - June 2025",
"reference_type": "invoice",
"reference_id": "inv_a1b2c3d4",
"due_date": "2025-06-30T00:00:00Z",
"approved_at": null,
"approved_by": null,
"executed_at": null,
"expected_tx_hash": null,
"verified_at": null,
"failed_at": null,
"canceled_at": null,
"failure_reason": null,
"environment": "live",
"created_at": "2025-06-15T10:00:00Z",
"updated_at": "2025-06-15T10:00:00Z"
}
Reference types
Link payouts to other entities in your system for tracking:
| Reference Type | Description |
|---|
checkout | Payout related to a specific checkout |
subscription | Payout related to a subscription payment |
invoice | Payout related to an invoice |
manual | Manual payout not linked to a specific entity |
Step 2: Approve the Payout
Review the payout intent and approve it when ready. This moves it from draft to approved.
curl -X PATCH https://api.billing.io/v1/payouts/po_e5f6a7b8c9d0e1f2a3b4c5d6 \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"status": "approved"
}'
You can also cancel a draft payout by setting status to canceled. Once
approved, the intent cannot be canceled through the API — you simply do not
execute the on-chain transfer.
Step 3: Execute the Payout Externally
This is the non-custodial step. You send the crypto from your wallet using whatever tool you prefer:
- A wallet application (MetaMask, TronLink, etc.)
- A programmatic script using ethers.js, web3.py, or tronweb
- A multisig wallet (Safe, etc.)
Important details when sending:
| Field | What to match |
|---|
| Recipient | The recipient_address from the payout intent |
| Token | The token specified (USDT or USDC) |
| Chain | The chain specified (tron, arbitrum, or base) |
| Amount | The amount from the payout intent |
Double-check the recipient address, chain, and token before executing.
On-chain transactions are irreversible. billing.io cannot recover funds
sent to the wrong address.
Step 4: Submit the Transaction Hash
After executing the on-chain transfer, submit the transaction hash to billing.io so the chain-watcher can verify it.
curl -X POST https://api.billing.io/v1/payouts/po_e5f6a7b8c9d0e1f2a3b4c5d6/execute \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"tx_hash": "abc123def456789abc123def456789abc123def456789abc123def456789abcd"
}'
Step 5: Chain-Watcher Verifies On-Chain
After you submit the transaction hash, billing.io’s chain-watcher service automatically:
- Detects the transaction on the specified blockchain
- Verifies the recipient, amount, and token match the payout intent
- Waits for sufficient block confirmations
- Creates a settlement record
- Updates the payout intent status to
settled
- Fires a
payout.settled webhook event
You do not need to take any action during this step. The payout intent moves through these statuses automatically:
executed -> verifying -> settled
\-> failed (if verification fails)
Payout intent status lifecycle
┌──────────┐
┌─────────┐ ┌──────────┐ ┌──────────┐ │ │
│ draft │ ──> │ approved │ ──> │ executed │ │ verifying│ ──> settled
└─────────┘ └──────────┘ └──────────┘ │ │
│ └──────────┘
│ │
v v
┌──────────┐ ┌──────────┐
│ canceled │ │ failed │
└──────────┘ └──────────┘
All possible statuses:
| Status | Description |
|---|
draft | Intent created, awaiting approval |
approved | Approved, ready for you to execute on-chain |
pending_execution | Execution in progress |
executed | Transaction hash submitted, awaiting verification |
verifying | Chain-watcher is verifying the on-chain transaction |
settled | On-chain settlement confirmed (terminal) |
failed | Verification failed or transaction invalid (terminal) |
canceled | Payout was canceled before execution (terminal) |
Step 6: View Reconciliation
Reconciliation compares your payout intents against actual on-chain settlements to identify matches, gaps, and discrepancies.
View settlements
curl "https://api.billing.io/v1/payouts/settlements?payout_intent_id=po_e5f6a7b8c9d0e1f2a3b4c5d6" \
-H "Authorization: Bearer sk_live_xxx"
View reconciliation summary
The reconciliation endpoint provides a high-level summary of matched, unmatched, and discrepant payouts:
curl https://api.billing.io/v1/payouts/reconciliation \
-H "Authorization: Bearer sk_live_xxx"
Response:
{
"matched": {
"count": 42,
"total_amount": 25000.00
},
"unmatched_payouts": {
"count": 2,
"total_amount": 1500.00
},
"unexpected_settlements": {
"count": 0,
"total_amount": 0
},
"discrepancies": [
{
"payout_intent_id": "po_abc123",
"settlement_id": "stl_def456",
"expected_amount": 500.00,
"actual_amount": 499.95,
"difference": -0.05,
"chain": "tron"
}
]
}
Understanding reconciliation results
| Category | Description |
|---|
| Matched | Payout intents with confirmed on-chain settlements that match in amount |
| Unmatched Payouts | Payout intents that were approved/executed but have no matching settlement yet |
| Unexpected Settlements | On-chain settlements detected that do not match any payout intent |
| Discrepancies | Matched payouts where the expected and actual amounts differ |
Small discrepancies can occur due to token precision differences or rounding.
The difference field shows the exact delta. Review discrepancies regularly
through the dashboard at Payouts > Reconciliation.
Complete Payout Flow
┌─────────────┐ ┌─────────────┐
│ Your Server │ │ billing.io │
│ │ │ API │
│ │ POST /payouts │ │
│ │ ──────────────────────────────────> │ draft │
│ │ │ │
│ │ PATCH /payouts/{id} (approve) │ │
│ │ ──────────────────────────────────> │ approved │
│ │ │ │
│ Execute tx │ │ │
│ on-chain │ │ │
│ (your wallet) │ │
│ │ │ │
│ │ POST /payouts/{id}/execute │ │
│ │ ──────────────────────────────────> │ executed │
│ │ │ │
│ │ │ Chain │
│ │ │ Watcher │
│ │ │ verifies... │
│ │ │ │
│ │ <── webhook: payout.settled │ settled │
│ │ │ │
│ │ GET /payouts/reconciliation │ │
│ │ ──────────────────────────────────> │ summary │
└─────────────┘ └─────────────┘
Next Steps