reconcile command later settles or releases the payment only on proof from the Algorand indexer.
What an UNRESOLVED payment is
A downstream payment becomesUNRESOLVED when a signed payment has left Selo, or may have left it, and Selo has no settlement it can confirm. That happens when:
- your endpoint answered without a settlement transaction id;
- your endpoint claimed a transaction id different from the transfer Selo signed;
- the claimed transaction id is already recorded on another payment;
- your endpoint rejected the signed payment with a
402(a signed transfer can still be submitted until it expires); - the request timed out or lost its connection after it left Selo, or a
402carried an undecodablePAYMENT-RESPONSE; - Selo hit an internal error, or restarted, while the payment was open.
See Payments and spend limits for why a timeout never releases capacity.
Why absence is not proof
An Algorand transaction is valid only up to its last valid round. Until the network passes that round, a signed transfer that is not on chain yet can still be submitted and confirmed. Once the network is past it, the transfer can never be confirmed. So there are exactly two kinds of proof:- It settled: the indexer shows a confirmed transfer that matches the payment.
- It never will: the indexer does not have Selo’s own signed transfer, and the indexer’s current round is past that transfer’s last valid round.
The reconcile command
Reconciliation is an operator command run against Selo’s own database. It never signs, never pays and never changes a verdict.Terminal
How each payment is judged
1
Wait for the verdict
A payment whose job has not reached a verdict is held with
job_active. Reconciliation runs only after the job has concluded.2
Pick the network
The payment’s network must be Algorand Mainnet or Testnet; otherwise it is held with
unsupported_network.3
Pick the transaction id
Selo looks up the transfer it signed (
expectedTxId). If no signed id was derived, it falls back to the id your endpoint claimed. With neither, the payment is held with no_transaction_id and the indexer is not asked.4
Ask the indexer
One
GET <indexer>/v2/transactions?txid=<id> to https://mainnet-idx.algonode.cloud or https://testnet-idx.algonode.cloud, with a 10-second timeout, redirects refused and the answer capped at 262,144 bytes. The answer gives the transaction, if known, and the indexer’s current round.5
Decide
Settle, release or hold, using the table below.
Decision table
Each change is one conditional update
A settle or release is a conditionalUPDATE on the payment row that applies only while its status is still UNRESOLVED, plus the matching move of the job’s counters, in one transaction:
The payment keeps the reason it was held: the new resolution reason is appended in front of it, for example
reconciled: confirmed on chain in round <n> as tx <txId>; held because: <original reason>.
Because each change is conditional, reconciliation is safe to repeat and to run in parallel:
- Two reconcilers on separate connections settle or release a payment exactly once; the other sees it as already resolved.
- A second run after a settlement finds nothing to do and changes nothing.
- A held payment stays exactly as it was across repeated runs.
- A run stopped after reading converges on the next run, and its late write changes nothing.
- If the job’s counters cannot move, the payment change rolls back with them.
Output
Output format
1 while any payment in the database is still UNRESOLVED or any row failed, and 0 once nothing is left to resolve. Every held payment raises a downstream_still_unresolved alert with its reason, and every failed row a downstream_reconcile_failed alert.
What you see afterwards
- The private report reads the payment rows each time you open it. A reconciled payment shows its new status, a settled one adds its transaction id to
money.downstreamTxIds, andmoney.unresolvedSpendUsdcdrops. - The verdict and its reason do not change. A run whose payment was unresolved stays INCONCLUSIVE, because the test itself could not confirm the payment when it ran.
Payments signed before 26 October 2026
Selo has recorded each signed payment’s last valid round since 26 October 2026. Payments signed before then have no last valid round, so reconciliation can settle them on a confirmed matching transfer but can never release them.Held inbound payments
Reconciliation covers downstream payments only. When the facilitator does not confirm the outcome of your payment to Selo, the job is held INCONCLUSIVE withFACILITATOR_UNAVAILABLE, never runs, and answers 502 INBOUND_SETTLEMENT_UNRESOLVED. Do not pay again for that request. Such a held inbound settlement is still resolved by hand. See Recover an interrupted run.
Related
Payments and spend limits
The payment states and the budget invariant.
Verdicts and the gate
Why
PAYMENT_UNRESOLVED blocks a release.Recover an interrupted run
What to do when your own payment to Selo is unconfirmed.
Known limitations
What is still handled by hand.