Skip to content

Reconcile Payment Transactions ​

Use the ledger as the finalized balance-movement record and the deposit or withdrawal detail as the business-status record.

EventCustomer accountMerchant payment account
External USDT depositDEPOSIT / CREDIT—
Customer paymentINTERNAL_OUT / DEBITINTERNAL_IN / CREDIT
External settlement—WITHDRAW / DEBIT

The transaction type codes are DEPOSIT=1000, WITHDRAW=2000, INTERNAL_OUT=3000, INTERNAL_IN=4000, and ADJUSTMENT=5000.

Query both account ledgers ​

bash
wallet_request GET "/accounts/$USER_ACCOUNT_ID/ledger?asset_type_id=$ASSET_TYPE_ID&after_id=0&limit=100" | jq
wallet_request GET "/accounts/$PAYMENT_ACCOUNT_ID/ledger?asset_type_id=$ASSET_TYPE_ID&after_id=0&limit=100" | jq

Link the customer INTERNAL_OUT and merchant INTERNAL_IN entries through their shared ref_id, then retrieve GET /withdrawals/{ref_id} when detailed payment status is required.

Retrieve a single ledger entry ​

Keep account_id and ledger_id together; a ledger ID belongs to one account.

bash
export USER_LEDGER_ID='{ledger-id-from-customer-list}'
export PAYMENT_LEDGER_ID='{ledger-id-from-merchant-list}'
wallet_request GET "/accounts/$USER_ACCOUNT_ID/ledger/$USER_LEDGER_ID" | jq
wallet_request GET "/accounts/$PAYMENT_ACCOUNT_ID/ledger/$PAYMENT_LEDGER_ID" | jq

Synchronize incrementally ​

Maintain an independent cursor for each account and resource:

  • customer external deposits
  • customer-sent payments
  • merchant-sent withdrawals
  • customer ledger
  • merchant ledger

Initialize to 0 only when starting the first full synchronization. On restart, restore each resource/account/filter cursor from your database rather than overwriting it with the following values.

bash
export USER_DEPOSIT_CURSOR='0'
export USER_WITHDRAWAL_CURSOR='0'
export PAYMENT_WITHDRAWAL_CURSOR='0'
export USER_LEDGER_CURSOR='0'
export PAYMENT_LEDGER_CURSOR='0'
bash
wallet_request GET "/deposits?account_id=$USER_ACCOUNT_ID&type=EXTERNAL&after_id=$USER_DEPOSIT_CURSOR&limit=100" | jq
wallet_request GET "/withdrawals?account_id=$USER_ACCOUNT_ID&after_id=$USER_WITHDRAWAL_CURSOR&limit=100" | jq
wallet_request GET "/withdrawals?account_id=$PAYMENT_ACCOUNT_ID&after_id=$PAYMENT_WITHDRAWAL_CURSOR&limit=100" | jq
wallet_request GET "/accounts/$USER_ACCOUNT_ID/ledger?asset_type_id=$ASSET_TYPE_ID&after_id=$USER_LEDGER_CURSOR&limit=100" | jq
wallet_request GET "/accounts/$PAYMENT_ACCOUNT_ID/ledger?asset_type_id=$ASSET_TYPE_ID&after_id=$PAYMENT_LEDGER_CURSOR&limit=100" | jq

For every response:

  1. Upsert items idempotently by deposit_id, withdrawal_id, or ledger_id.
  2. Save next_cursor in the same database transaction where possible.
  3. Continue immediately while has_more is true.
  4. Resume from the saved cursor on the next poll.

Identify ledger references by both ref_type and ref_id. ref_type=1000 identifies a deposit; ref_type=2000 identifies a withdrawal or internal transfer. amount_raw is an absolute value: apply its direction using entry_type, and distinguish principal and fee entries with entry_code. Use balance_after_raw when comparing ledger totals to balances.

ID cursors return newly created records, not later status changes. Poll non-final deposits and withdrawals by their detail endpoints until each reaches a final state. The append-only ledger can be synchronized only by cursor.

Next: Build the complete payment flow.

OOLIN is a digital asset operations infrastructure product by BPMG.