Skip to main content
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 TypeDescription
checkoutPayout related to a specific checkout
subscriptionPayout related to a subscription payment
invoicePayout related to an invoice
manualManual 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:
FieldWhat to match
RecipientThe recipient_address from the payout intent
TokenThe token specified (USDT or USDC)
ChainThe chain specified (tron, arbitrum, or base)
AmountThe 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:
  1. Detects the transaction on the specified blockchain
  2. Verifies the recipient, amount, and token match the payout intent
  3. Waits for sufficient block confirmations
  4. Creates a settlement record
  5. Updates the payout intent status to settled
  6. 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:
StatusDescription
draftIntent created, awaiting approval
approvedApproved, ready for you to execute on-chain
pending_executionExecution in progress
executedTransaction hash submitted, awaiting verification
verifyingChain-watcher is verifying the on-chain transaction
settledOn-chain settlement confirmed (terminal)
failedVerification failed or transaction invalid (terminal)
canceledPayout 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

CategoryDescription
MatchedPayout intents with confirmed on-chain settlements that match in amount
Unmatched PayoutsPayout intents that were approved/executed but have no matching settlement yet
Unexpected SettlementsOn-chain settlements detected that do not match any payout intent
DiscrepanciesMatched 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