23.105. DD 104: Wire Gateway Management API#
- Design status:
Draft
- Implementation status:
Not started
- DD shepherd:
Antoine d’Aligny
- Historical contributors:
Antoine d’Aligny
- First published:
2026-09-18
- Last substantive change:
2026-09-18
23.105.1. Summary#
This design document presents the challenges we currently face when handling transaction failures in wire gateway adapters and proposes a solution for addressing them.
23.105.2. Motivation#
In the current design and implementations, both incoming and outgoing transactions can fail without administrators being notified or having the ability to take corrective action.
23.105.3. Requirements#
The solution must allow administrators to see failures, understand their causes, and possibly fix them.
The solution must be lightweight to implement in wire gateway adapters, as the goal of this API is to provide a thin wrapper around how each implementation works. Common logic that can be extracted into another component should be.
23.105.4. Proposed Solution#
23.105.4.1. Terminology#
in_tx: final incoming transaction
out_tx: final outgoing transaction
talerable_in_tx: talerable final incoming transaction (RESERVE | KYC | MAP)
talerable_out_tx: talerable final outgoing transaction (deposit)
transfer: initiated talerable outgoing transaction
initiated: initiated outgoing transaction
bounce: bounced invalid outgoing transaction
23.105.4.2. Current API State#
We already have endpoints for querying Talerable transactions: talerable_in_tx with GET /history/incoming and talerable_out_tx with GET /history/outgoing.
We also already have endpoints for querying transfer with GET /transfers and GET /transfers/$ROW_ID.
What is missing is access to raw (incompled, malformed, etc) transactions, in_tx and out_tx, as well as initiated and bounced transactions, initiated and bounce.
23.105.4.3. Generic API Design#
Each adapter works differently. In most adapters, transfer and bounce produce an initiated operation, while other payment systems, such as Cyclos, provide a proper way to perform a bounce idempotently and have a specific bounce operation.
Each adapter also has different internal unique identifiers. For example, libeufin_nexus uses three of them while depolymerizer-bitcoin has a single transaction ID.
We need an API that can expose enough detail for manual reconciliation while remaining generic enough to reuse the same logic across all adapters.
For transfer, we already have the paginated GET /transfers endpoint and the single-entry GET /transfers/$ROW_ID endpoint. The problem with this design is that it is not well suited to following the progress of transfers. Transfers do not progress or reach finality in the order in which they are created.
I propose adding a new status-history endpoint, GET /transfers/status-history. This will allow administrators to understand the lifecycle of a transaction and track the progress of all transfers in real time.
We also need new endpoints for incoming and outgoing transactions, including paginated and single-entry endpoints, so that administrators can inspect all transactions, including incomplete or invalid ones. This is necessary to understand and debug why an incoming transaction never reaches the Talerable history.
Finally, we need a way to track both initiated and bounce transactions using new endpoints providing paginated listings, status histories and single-entry views.
We could potentially remove all transfer endpoints, since transfers are a subset of initiated operatuibs. Whether to do so depends on whether the transfer-specific API remains useful as a higher-level abstraction.
We could also enforce a single initiated operation concept, event if underneath bounces works differently. As long as doing this it not too messy for the database layer we should pursue this simplification.
interface InTx {
// Opaque identifier of the returned record.
row_id: SafeUint64;
// Unstructured implementation specific fields that must be shown.
// Contains at least the unique identifiers to be used for manual reconciliation.
details: {string: string};
// Date of the transaction.
date: Timestamp;
// Amount received before credit_fee.
amount?: Amount;
// Unstructured transaction subject
subject?: string;
// Fee paid by the creditor.
// If not null, creditor actually received amount - credit_fee
credit_fee?: Amount;
// Full payto URI to identify the sender of funds.
debit_account?: string;
// ID of the bounce operation if bounced
bounce_id?: SafeUint64;
}
interface OutTx {
// Opaque identifier of the returned record.
row_id: SafeUint64;
// Unstructured implementation specific fields that must be shown.
// Contains at least the unique identifiers to be used for manual reconciliation.
details: {string: string};
// Date of the transaction.
date: Timestamp;
// Amount received before credit_fee.
amount?: Amount;
// Unstructured transaction subject
subject?: string;
// Fee paid by the debtor.
// If not null, debtor actually paid amount + debit_fee
debit_fee?: Amount;
// Full payto URI to identify the receiver of funds.
credit_account?: string;
}
interface InitiatedTx {
// Opaque ID of this operation.
operation_id: SafeUint64;
// Amount to transfer.
amount: Amount;
// The recipient's account identifier as a full payto:// URI.
credit_account: string;
// Optional ID of the wire transfer initiation if operation is a transfer
transfer_id?: SafeUint64;
// Optional ID of the bounced incoming transaction if operation is a bounce
bounced_id?: SafeUint64;
// Current status
// pending: in progress
// transient_failure: has failed but may succeed later
// permanent_failure: has failed permanently and will never reach finality
// success: has succeeded and reached finality
// late_failure: has failed permanently after reaching the success status
status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
// Timestamp that indicates when this status was reached.
timestamp: Timestamp;
}
interface InitiatedStatus {
// Opaque ID of this operation.
operation_id: SafeUint64;
// Opaque ID of this status, used for pagination.
row_id: SafeUint64;
// pending: in progress
// transient_failure: has failed but may succeed later
// permanent_failure: has failed permanently and will never reach finality
// success: has succeeded and reached finality
// late_failure: has failed permanently after reaching the success status
status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
// Optional unstructured messages about the status. Can be used to document the reasons for failure or the state of progress.
status_msg?: string;
// Timestamp that indicates when this status was reached.
timestamp: Timestamp;
}
23.105.4.4. Management logic#
On top of this API, we could build a Management SPA to support administrative operations. The UI would show recent failures and track whether someone has reviewed them and made a decision on how to handle them. We could also extend the API with administrative operations, such as manually requesting a retry.