1.11.3. Taler Wire Gateway HTTP API

This section describes the API offered by the Taler wire gateway. The API is used by the exchange to trigger transactions and query incoming transactions, as well as by the auditor to query incoming and outgoing transactions.

This API is currently implemented by the Taler Demo Bank, as well as by LibEuFin.

GET /config

Return the protocol version and configuration information about the bank. This specification corresponds to current protocol being version 3.

Response:

200 OK:

The exchange responds with a WireConfig object. This request should virtually always be successful.

Details:

interface WireConfig {
  // Name of the API.
  name: "taler-wire-gateway";

  // libtool-style representation of the Bank protocol version, see
  // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
  // The format is "current:revision:age".
  version: string;

  // Currency used by this gateway.
  currency: string;

  // URN of the implementation (needed to interpret 'revision' in version).
  // @since v0, may become mandatory in the future.
  implementation?: string;
}

1.11.3.1. Authentication

The bank library authenticates requests to the wire gateway via HTTP basic auth.

1.11.3.2. Making Transactions

POST /transfer

Initiate a new wire transfer from the exchange’s bank account, typically to a merchant.

The exchange’s bank account is not included in the request, but instead derived from the username in the Authorization header and/or the request base URL.

To make the API idempotent, the client must include a nonce. Requests with the same nonce are rejected unless the request is the same.

Request:

interface TransferRequest {
  // Nonce to make the request idempotent.  Requests with the same
  // request_uid that differs in any of the other fields
  // are rejected.
  request_uid: HashCode;

  // Amount to transfer.
  amount: Amount;

  // Base URL of the exchange.  Shall be included by the bank gateway
  // in the appropriate section of the wire transfer details.
  exchange_base_url: string;

  // Wire transfer identifier chosen by the exchange,
  // used by the merchant to identify the Taler order(s)
  // associated with this wire transfer.
  wtid: ShortHashCode;

  // The recipient's account identifier as a full payto URI.
  credit_account: string;
}

Response:

200 OK:

The request has been correctly handled, so the funds have been transferred to the recipient’s account. The body is a TransferResponse.

400 Bad request:

Request malformed. The bank replies with an ErrorDetail object.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown. The bank replies with an ErrorDetail object.

409 Conflict:

A transaction with the same request_uid but different transaction details has been submitted before.

Details:

interface TransferResponse {
  // Timestamp that indicates when the wire transfer will be executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;

  // Opaque ID of the wire transfer initiation performed by the bank.
  // It is different from the /history endpoints row_id.
  row_id: SafeUint64;
}
GET /transfers

Return a list of transfers initiated from the exchange.

The bank account of the exchange is determined via the base URL and/or the user name in the Authorization header. The transfer history might come from a “virtual” account, where multiple real bank accounts are merged into one history.

Since protocol v3.

Request:

Query Parameters:
  • limitOptional. At most return the given number of results. Negative for descending by row_id, positive for ascending by row_id. Defaults to -20.

  • offsetOptional. Starting row_id for pagination.

  • statusOptional. Filters by status.

Response:

200 OK:

JSON object of type TransferList.

204 No content:

There are no transfers to report (under the given filter).

400 Bad request:

Request malformed.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown.

Details:

interface TransferList {
  // Array of initiated transfers.
  transfers: TransferListStatus[];

  // Full payto:// URI to identify the sender of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.
  debit_account: string;
}
interface TransferListStatus {
  // Opaque ID of the wire transfer initiation performed by the bank.
  // It is different from the /history endpoints row_id.
  row_id: SafeUint64;

  // Current status of the transfer
  // pending: the transfer is in progress
  // transient_failure: the transfer has failed but may succeed later
  // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
  // success: the transfer has succeeded  and appears in the outgoing history
  status: "pending" | "transient_failure" | "permanent_failure" | "success";

  // Amount to transfer.
  amount: Amount;

  // The recipient's account identifier as a full payto:// URI.
  credit_account: string;

  // Timestamp that indicates when the wire transfer was executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;
}
GET /transfers/$ROW_ID

Return the status of a transfer initiated from the exchange, identified by the ROW_ID.

Since protocol v3.

Response:

200 OK:

The transfer is known, and details are given in the TransferStatus response body.

400 Bad request:

Request malformed.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The transfer was not found.

Details:

interface TransferStatus {
  // Current status of the transfer
  // pending: the transfer is in progress
  // transient_failure: the transfer has failed but may succeed later
  // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
  // success: the transfer has succeeded  and appears in the outgoing history
  status: "pending" | "transient_failure" | "permanent_failure" | "success";

  // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress.
  status_msg?: string;

  // Amount to transfer.
  amount: Amount;

  // Base URL of the exchange.  Shall be included by the bank gateway
  // in the appropriate section of the wire transfer details.
  exchange_base_url: string;

  // Wire transfer identifier chosen by the exchange,
  // used by the merchant to identify the Taler order(s)
  // associated with this wire transfer.
  wtid: ShortHashCode;

  // The recipient's account identifier as a full payto URI.
  credit_account: string;

  // Timestamp that indicates when the wire transfer was executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;
}

1.11.3.3. Querying the transaction history

The exchange’s bank account is derived from the username in the Authorization header and/or the request’s base URL. In fact, the transaction history may come from a “virtual” account, where several real bank accounts are merged into a single history.

GET /history/incoming

Return a list of transactions made from or to the exchange.

Incoming transactions must contain a valid reserve public key. If a bank transaction does not conform to the right syntax, the wire gateway must not report it to the exchange, and send funds back to the sender if possible.

Request:

Query Parameters:
  • limitOptional. At most return the given number of results. Negative for descending by row_id, positive for ascending by row_id. Defaults to -20. Since protocol v2.

  • offsetOptional. Starting row_id for pagination. Since protocol v2.

  • timeout_msOptional. Timeout in milliseconds, for long-polling, to wait for at least one element to be shown. Only useful if limit is positive. Since protocol v2.

  • deltaOptional. Deprecated in protocol v2. Use limit instead.

  • startOptional. Deprecated in protocol v2. Use offset instead.

  • long_poll_msOptional. Deprecated in protocol v2. Use timeout_ms instead.

Response:

200 OK:

JSON object of type IncomingHistory.

204 No content:

There are not transactions to report (under the given filter).

400 Bad request:

Request malformed. The bank replies with an ErrorDetail object.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown. The bank replies with an ErrorDetail object.

Details:

interface IncomingHistory {
  // Array of incoming transactions.
  incoming_transactions: IncomingBankTransaction[];

  // Full payto URI to identify the receiver of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.
  credit_account: string;
}
// Union discriminated by the "type" field.
type IncomingBankTransaction =
| IncomingKycAuthTransaction
| IncomingReserveTransaction
| IncomingWadTransaction;
// Since protocol **v1**.
interface IncomingKycAuthTransaction {
  type: "KYCAUTH";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount received before credit_fee.
  amount: Amount;

  // Fee payed by the creditor.
  // If not null, creditor actually received amount - credit_fee
  // @since **v3**
  credit_fee?: Amount;

  // Full payto URI to identify the sender of funds.
  debit_account: string;

  // The account public key extracted from the transaction details.
  account_pub: EddsaPublicKey;
}
interface IncomingReserveTransaction {
  type: "RESERVE";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount received before credit_fee.
  amount: Amount;

  // Fee payed by the creditor.
  // If not null, creditor actually received amount -
  // @since **v3**
  credit_fee?: Amount;

  // Full payto URI to identify the sender of funds.
  debit_account: string;

  // The reserve public key extracted from the transaction details.
  reserve_pub: EddsaPublicKey;
}
interface IncomingWadTransaction {
  type: "WAD";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount received before credit_fee.
  amount: Amount;

  // Fee payed by the creditor.
  // If not null, creditor actually received amount - credit_fee
  // @since **v3**
  credit_fee?: Amount;

  // Full payto URI to identify the sender of funds.
  debit_account: string;

  // Base URL of the exchange that originated the wad.
  origin_exchange_url: string;

  // The reserve public key extracted from the transaction details.
  wad_id: WadId;
}
GET /history/outgoing

Return a list of transactions made by the exchange, typically to a merchant.

Request:

Query Parameters:
  • limitOptional. At most return the given number of results. Negative for descending by row_id, positive for ascending by row_id. Defaults to -20. Since protocol v2.

  • offsetOptional. Starting row_id for pagination. Since protocol v2.

  • timeout_msOptional. Timeout in milliseconds, for long-polling, to wait for at least one element to be shown. Only useful if limit is positive. Since protocol v2.

  • deltaOptional. Deprecated in protocol v2. Use limit instead.

  • startOptional. Deprecated in protocol v2. Use offset instead.

  • long_poll_msOptional. Deprecated in protocol v2. Use timeout_ms instead.

Response:

200 OK:

JSON object of type OutgoingHistory.

204 No content:

There are not transactions to report (under the given filter).

400 Bad request:

Request malformed. The bank replies with an ErrorDetail object.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown. The bank replies with an ErrorDetail object.

Details:

interface OutgoingHistory {
  // Array of outgoing transactions.
  outgoing_transactions: OutgoingBankTransaction[];

  // Full payto URI to identify the sender of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.
  debit_account: string;
}
interface OutgoingBankTransaction {
  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount transferred.
  amount: Amount;

  // Full payto URI to identify the receiver of funds.
  credit_account: string;

  // The wire transfer ID in the outgoing transaction.
  wtid: ShortHashCode;

  // Base URL of the exchange.
  exchange_base_url: string;
}

1.11.3.4. Wire Transfer Test APIs

Endpoints in this section are only used for integration tests and never exposed by bank gateways in production.

POST /admin/add-incoming

Simulate a transfer from a customer to the exchange. This API is not idempotent since it’s only used in testing.

Request:

interface AddIncomingRequest {
  // Amount to transfer.
  amount: Amount;

  // Reserve public key that is included in the wire transfer details
  // to identify the reserve that is being topped up.
  reserve_pub: EddsaPublicKey;

  // Account (as full payto URI) that makes the wire transfer to the exchange.
  // Usually this account must be created by the test harness before this
  // API is used. An exception is the "fakebank", where any debit account
  // can be specified, as it is automatically created.
  debit_account: string;
}

Response:

200 OK:

The request has been correctly handled, so the funds have been transferred to the recipient’s account. The body is a AddIncomingResponse.

400 Bad request:

The request is malformed. The bank replies with an ErrorDetail object.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown. The bank replies with an ErrorDetail object.

409 Conflict:

The ‘reserve_pub’ argument was used previously in another transfer, and the specification mandates that reserve public keys must not be reused.

Details:

interface AddIncomingResponse {
  // Timestamp that indicates when the wire transfer will be executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;

  // Opaque ID of the wire transfer initiation performed by the bank.
  // It is different from the /history endpoints row_id.
  row_id: SafeUint64;
}
POST /admin/add-kycauth

Simulate a transfer from a customer to the exchange. This API is not idempotent since it’s only used in testing.

Request:

interface AddKycauthRequest {
  // Amount to transfer.
  amount: Amount;

  // Account public key that is included in the wire transfer details
  // to associate this key with the originating bank account.
  account_pub: EddsaPublicKey;

  // Account (as full payto URI) that makes the wire transfer to the exchange.
  // Usually this account must be created by the test harness before this
  // API is used. An exception is the "fakebank", where any debit account
  // can be specified, as it is automatically created.
  debit_account: string;
}

Response:

200 OK:

The request has been correctly handled, so the funds have been transferred to the recipient’s account. The body is a AddKycauthResponse.

400 Bad request:

The request is malformed. The bank replies with an ErrorDetail object.

401 Unauthorized:

Authentication failed, likely the credentials are wrong.

404 Not found:

The endpoint is wrong or the user name is unknown. The bank replies with an ErrorDetail object.

Details:

interface AddKycauthResponse {
  // Timestamp that indicates when the wire transfer will be executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;

  // Opaque ID of the wire transfer initiation performed by the bank.
  // It is different from the /history endpoints row_id.
  row_id: SafeUint64;
}

1.11.3.4.1. Security Considerations

For implementors:

  • The withdrawal operation ID must contain enough entropy to be unguessable.

Design:

  • The user must complete the 2FA step of the withdrawal in the context of their banking app or online banking Website. We explicitly reject any design where the user would have to enter a confirmation code they get from their bank in the context of the wallet, as this would teach and normalize bad security habits.