Token Bank Integration
TokenOS interacts with banks via APIs. A bank can provide tighter integration with TokenOS by implementing TokenOS’s bank integration API. (If a bank doesn’t implement this API, but does offer a public API, then TokenOS might use that other API. Implementing TokenOS’ bank integration API enables more features and smoother user experience for that bank.) By means of the TokenOS bank integration API, a bank can
- Offer online merchants bank-direct payments capabilities with superior customer experience
- Acquire new retail customers
- Retain existing retail customers
- Build new products and services for corporate clients
- Offer clients products and services from the Token developer community
The Token Bank Integration Java SDK simplifies handling the TokenOS bank integration API. It handles network requests by calling Java methods; this allows programmers to concentrate on business logic instead of remote procedure call logic.
A Bank’s steps to integrating with TokenOS:
Get the Token Bank Integration SDK. Get the sample bank server implementation.
Generate keys and certificates for secure communication; keep track of them.
Set up fake environment/data for testing. This probably means setting up an environment with some fake accounts.
Get the sample bank server up and running. Normally this just works, but there are troubleshooting tips.
Ask Token to send test requests to the server. As the Bank gets some parts working with real functionality, Token can turn on more tests.
Replace fake parts of the sample bank server implementation with real implementations.
Set up a web interaction that authenticates a Bank customer and lets them choose accounts to link to Token.
The bank sets up a server that opens a port and listens for connections from TokenOS. Rather than implementing a server “from scratch”, a bank can start with the sample implementation. The sample responds to requests with preconfigured data; it doesn’t persist state, but instead “forgets” everything when reset.
API Overview
The bank server implementation handles TokenOS requests by defining methods. The Token Bank Integration SDK defines some interfaces. The bank implements these interfaces; when TokenOS sends a request to the bank, the SDK turns that request into an interface method call.
To handle these requests, a bank implements some interfaces. Depending on which features the bank will support, it must implement some or all of these:
AccountLinkingService
(javadoc, description): Associates a Token member with Bank account(s)getBankAuthorization
: get account linking information
AccountService
(javadoc, description): Requests for bank account information.getBalance
: get account balancegetTransactions
,getTransaction
: get information about past transactionsresolveTransferDestination
: get transfer destination choicesgetAccountDetails
: get detailed information about an account
TransferService
(javadoc, description) Handles requests for transfers.transfer
: Transfer funds
StorageService
(javadoc) Handles requests to store key-value data. Keys are strings; values are binary “blobs”. Most banks won’t implement this service. Banks that don’t let Token store customer data in the Token Cloud implementStorageService
.setValue
: Store dataremoveValue
: Delete datagetValue
: Return data
NotificationService
(javadoc, description) Most banks won’t implement this service. A bank can register to deliver Token notifications to bank customers. Such a bank uses the Notification Service as part of delivering those notifications.notify
: Deliver a notification
Many parts of the Bank Integration API work with a bank’s accounts: ask account balance, request transfer, and so on. A few parts of this API also refer to Token members, IDs of Token users. Where you see “account”, think bank account; where you see “member”, think Token member.
In TokenOS, an account has exactly one currency. Banks might support multicurrency bank accounts; to work with TokenOS, treat such an account as multiple Token “accounts”, one per currency. A TokenOS bank account has metadata. To “tag” one TokenOS account for one currency and another TokenOS for another currency, use metadata.
Moving money involves Transfers and Tokens. A Transfer represents a request to move money from one account to another. A transfer can have two Transactions associated with it: a debit at Bank A and a credit at Bank B. A Token is an authorization to access an underlying asset; when TokenOS keeps track of whether a payer authorizes a payment, it uses a Token to do so. You can learn more about Tokens.
// TokenOS treats these as distinct accounts:
BankAccount eurAcct = BankAccount.newBuilder()
.setSwift(swift)
.setAccountFeatures(features)
.putMetadata("currency", "EUR")
.build();
BankAccount gbpAcct = BankAccount.newBuilder()
.setSwift(swift) // same
.setAccountFeatures(features) // same
.putMetadata("currency", "GBP") // different
.build();
Account Linking Web Flow
The user sets up Token to link her bank account(s) with her Token member. Until the user has at least one bank account associated with her Token member, she can’t do any useful banking things.
The user interacts with the bank’s website to demonstrate authority over accounts; the user’s application sends proof of this interaction to Token; Token sends this proof to the Bank’s server, which responds with account linking information.
Overview / UI Flow
When a user wants to set up Token with her bank accounts at a Bank:
The user launches the Token app on her phone.
The Token app shows a list of banks. The user chooses her Bank.
The Token app shows a screen that prompts the user to log in to her Bank. This screen is a web view showing a web page provided by the Bank. When onboarding, the Bank told Token the URL of this page. Token passes in
memberId=
token member id andredirect_uri=
uri as URL parameters.The user completes 2FA (two-factor authentication) triggered by the Bank, similar to their internet banking login process.
The user logs in, sees a list of her link-able accounts; she checks those accounts she wants to link. This is a web form provided by the Bank web site.
The user OKs the web form. The Bank’s web site stores information about the linked accounts, associating that information with a “bearer token”: a short, unguessable string.
The Bank’s web site redirects to the location in the earlier-specified
redirect_uri
parameter, appending#access_token=
bearer token stringWhen the web view’s location changes to this URL, the app gets the bearer token string and stops showing the web view.
The app uses the Token SDK to send this bearer token string to TokenOS.
Using the bearer token string, TokenOS requests linking information from the bank’s RPC server. The bank’s
AccountLinkingService
handles the request, returning aBankAuthorization
structure containing the member ID and account information.TokenOS links the accounts to the Token member. (The Bank does not need to write code to handle this request, unless it’s configured to not store customer data in the Token Cloud, in which case it implements
StorageService
.)
To get all this working, the Bank’s code must do a few things:
Set up a web UI flow separate from the Bank Integration API that has the user log in and choose from a list of accounts. This is described below.
Store account-choice information indexed by bearer token string such that it can be retrieved by the bank server’s
AccountLinkingService
.Implement the
AccountLinkingService
.Provision the Bank web site with a crypto configuration which the Bank shares with Token.
It’s normal to associate more than one Token member with a bank account. For example, if the bank account is a joint account, then more than one user might link their Token members with it.
Bank Web Site
As part of enabling Token linking, the Bank must set up some forms on the Bank web site, generate some data that links a Token member ID to their account(s), store that account-choice information, and redirect.
IMPORTANT: The bank’s site must use https
instead of http
and the certificate must be from a valid Certificate authority, NOT a self-signed one.
(This implements the Implicit Grant part of the OAuth2.0 spec.)
Prompt the user to sign in; authenticate the user. Once she authenticates…
Present a form with a checkbox for each of the user’s bank accounts. If the user has some accounts which shouldn’t be Token-linkable, don’t show those accounts. If the user wants to Cancel, pretend she didn’t select any accounts and chose OK. If the user OKs…
Store the account choice information. Later, the Bank service will need to retrieve this information using a bearer token string. Thus, it might make sense to store this information in a database indexed on the bearer token string.
Redirect as described below.
The start of this flow should be an endpoint whose path
ends in /authorize
, e.g. https://sample-bank.com/token/authorize
.
The Token app sets
?memberId=
string&redirect_uri=
string
URL parameters when it shows the
Bank’s sign-in web page. Keep track of these values, which are needed later.
The member ID string is a Token member ID; it probably is not the same as
the user’s Bank web site user name.
To respond to the client app with the bearer token string, redirect to
the URI: redirect_uri#access_token=bearer token string.
E.g., if redirect_uri
is https://token.io/
and the bearer token string
is ZmpkCmF
, then redirect to https://token.io/#access_token=ZmpkCmF
.
To instead respond to the client app with an error, redirect to the URI:
redirect_uri?error=error string, where
error string is one of invalid_request
,
access_denied
, server_error
, or temporarily_unavailable
.
https//token.io?error=access_denied
Guest Checkout
Payment Initiation Services (PIS)
User’s who make a purchase with Token have the option to complete the purchase without the necessity of a Token identity, even if their bank is integrated with Token. This is called Guest Checkout.
- The user is redirected to their bank’s authentication page (login).
- The user authenticates with the bank and chooses which account to use for payment.
- The bank displays a confirmation of payment.
- The user confirms the purchase with the bank.
Payment Initiation Flow
- The bank provides Token with a bank_url endpoint.
The user proceeds to make a Guest Checkout.
Token hits the URL in the following format:
GET bank_url?auth-request-id=auth_request_id
The auth-request-id is a unique identifier used to retrieve the payload containing all information on the purchase as well as the final callback URL. This payload needs to be signed by the bank, hence the name (Token is requesting authorization from the bank).
When the bank receives the above information, it calls
getAuthRequestPayload(authRequestId)
using Token’s Bank SDK, whereauthRequestId
is the unique identifier in the URL.This call returns a
TokenPayload
containing information about the purchase as well as the callback url.The bank displays its authentication pages for the user to authenticate, optionally including SCA (Strong Customer Authentication). This can be the same page as the one used for linking. If something goes wrong during authentication, the bank should redirect the user to the callback-url with the query parameter error and the received error code. This will be propagated back to the TPP.
After the user authenticates, the bank may create and display an account selection page for the user to choose which account they wish to pay from. If the user only has one account, this page may not be displayed. This page will need to be created by the bank.
Once the bank has the payload from Step 3 and the account the user chose in Step 5, it can retrieve the
(refId, initiatorId)
pair from the payload by callingpayload.getRefId()
andpayload.getInitiatorId()
The bank is responsible for linking this pair with the user’s chosen account. When the payment is redeemed, Token sends a TransferRequest containing a
(tokenRefId, tokenInitiatorId)
pair to the bank (this pair is the same asrefId
andinitiatorId
). The bank should use this pair to look up the user’s chosen account and perform the transfer.After the user selects the account, the bank displays the Payment Confirmation page containing information on the purchase. The bank parses and displays the payload from Step 3 on this page.
Once the user confirms the purchase, the bank signs the payload using Member:
SignTokenPayload
from the SDK and produces thebank_signature
. The result ofsignTokenPayload
needs to be base64 encoded, and the result of that is the signature param in the URL mentioned in Step 10.The bank redirects the user using the
callback_url
in the following format:callback_url?auth-request-id=auth_request_id&signature=bank_signature
When the merchant calls RedeemToken, Token sends a TransferRequest to the bank that contains the same
(tokenRefId, tokenInitiatorId)
pair mentioned in Step 6.The bank uses this pair to look up its associated user account (described in Step 6 of Payment Initiation), and perform the actual transfer of funds.
IMPORTANT: You must implement the getTransferStatus
endpoint.
NOTE: The Token Bank SDK is private. We will provide you with the Artifactory credentials for access. See this article for setting it up with Gradle.
Account Information Services (AIS)
The user has the ability to give TPPs access to their data through Token without the use of the Token App for directly integrated banks.
- The user is redirected to the bank’s authentication (login) page.
- The user authenticates with the bank and chooses which account they wish to grant the TPP access to (balance and transaction).
- The bank displays a confirmation page.
- The user confirms their consent for access.
Flow
This flow can currently be achieved by following the same procedure described in the Account Linking section. If you have already implemented our account linking flow, you do not need to do anything more.
NOTE: While the Account Linking Section above describes the Token Mobile App, everything that is relevant to the Guest Access Flow also applies to the Token Web App.
Below is a high-level overview of the procedure:
The bank provides Token with a
bank_url
to the bank login page.The user opens the Token web app on the AISP’s website.
Token shows a list of banks and the user chooses their bank.
They click Proceed as Guest, then Token redirects them to the bank login page (
bank_url
) with the query parameter linking-request-id=linking_request_id.The bank calls
GetLinkingRequest
to obtain theTokenRequest
,member_id
, andcallback_url
.The user logs in and sees a list of their linkable accounts; the user checks those accounts they want to link. This is a web form provided by the bank website.
The user “OK’s” the web form and the bank stores information about the linked accounts, associating that information with a short, unguessable string called a “bearer token”.
The bank’s web site redirects to the location in the earlier-specified redirect_uri parameter, appending the hash fragment #access_token=bearer_token.
The Token web app gets the
bearer_token
and sends it to TokenOS.Using the bearer_token, TokenOS requests linking information from the bank’s RPC server. The bank’s
AccountLinkingService
handles the request, returning aBankAuthorization structure
containing the member ID and account information.TokenOS links the accounts to a temporary Token member. (The bank does not need to write code to handle this request unless it’s configured not store customer data in the Token Cloud, in which case it implements
StorageService
).To make this work the bank must:
- Set up a web UI flow separate from the Bank Integration API that has the user login and chooses from a list of accounts. This is described above under Bank Web Site.
- Store account-choice information indexed by bearer token string so it can be retrieved by the bank server’s AccountLinkingService
.
- Implement the AccountLinkingService
.
- Provision the bank web site with a crypto configuration the bank shares with Token.
NOTE: It’s normal to associate more than one Token member with a bank account. If the user is a guest user, Token will create a temporary Token member each time the flow is executed.
IMPORTANT: The flow described above is identical to the account linking flow. We are working on improving it for guest access, so expect changes in the future.
Bank Integration SDK
The Bank programs a service to listen and respond to Token requests from the Internet. Token provides a Bank Integration Java SDK to ease this task. The Java SDK defines Java interfaces for the Bank to implement. It provides some code to convert structures sent over the network into simpler structures. Bank programmers can thus focus on core business logic.
The Sample implementation uses the Bank Integration SDK and uses its APIs.
The SDK defines a few interfaces. To support various Token features, a bank can implement some of these interfaces. If a bank will not support all Token features, it need not implement all of these interfaces. The interfaces are described in more detail below.
To get the latest Bank Integration Java SDK, browse https://token.jfrog.io/token/webapp/#/artifacts/browse/tree/General/public-libs-release-local/io/token/sdk/tokenio-sdk-integration and choose the latest version.
Maven
- To depend on the SDK, tell Maven to use Token’s artifact repository:
<repositories> ... <repository> <url>https://token.jfrog.io/token/public-libs-release-local/</url> </repository> </repositories>
- Specify the artifact to depend on:
<dependency> <groupId>io.token.sdk</groupId> <artifactId>tokenio-sdk-integration</artifactId> <version>2.1.5</version> </dependency>
Gradle
- To depend on the SDK, tell Gradle to use Token’s artifact repository:
repositories { ... maven { url 'https://token.jfrog.io/token/public-libs-release-local/' } }
- Specify the artifact to depend on:
compile(group: 'io.token.sdk', name: 'tokenio-sdk-integration', version: '2.1.5')
If you are interested in building your app with Android or iOS embedded SDK, you can find them here
Server Builder
Token Integration SDK provides a framework for wiring service implementations abstractions into a single Server running on a given port.
This example uses io.token.sdk.ServerBuilder
to run a custom implementation of the
Token Bank Service integrated with the Core Banking System of a partner bank.
This example starts a server listening on port 1234 and ready to serve requests from TokenOS. Its connectivity configuration was set up as part of onboarding.
You can optionally register an interceptor to execute some code at the
start or end of any RPC request the server receives.
Call the registerInterceptor
method; this method uses
InterceptorFactory
which uses SimpleInterceptor
.
This code uses the APIs:
ServerBuilder server = ServerBuilder
.forPort(args.port)
.reportErrorDetails()
.withAccountService(factory.accountService())
.withAccountLinkingService(factory.accountLinkingService())
.withTransferService(factory.transferService())
.withStorageService(factory.storageService());
if (args.useSsl) {
server.withTls(
args.configPath("tls", "cert.pem"),
args.configPath("tls", "key.pem"),
args.configPath("tls", "trusted-certs.pem"));
}
// You will need to Ctrl-C to exit.
server
.build()
.start()
.await();
Service Interface Definitions
The Token Bank Integration SDK defines some interfaces. Much of the programming effort in integrating a bank with Token goes into implementing these interfaces; much of the rest of this page describes them.
Common Objects
To program a service that handles Token requests, the Bank implements some interfaces using the Token Bank Integration SDK. Some data structures are used in a few methods.
Many important data structures are defined as
Protocol Buffers.
The build process generates Java code from these.
You can learn more about this Java
Code.
A quick start: If you have a Java object based on a protocol buffer definition,
to get the value of the field named tool_name
, there are methods
named getToolName…
; To create a Java object of a class based on
a protocol buffer definition named Tool
, call
Tool.newBuilder().setToolName("Nice tool").build();
.
You can get the newest protocol buffer definitions by downloading the most recent jars from
https://token.jfrog.io/token/list/public-libs-release-local/io/token/proto/.
For protocol buffers, you want the “regular” jars, not javadoc
or sources
.
To see these files as web pages, please see the
Bank Protocol Buffer Reference.
Money
An object to represent a monetary value in a specific currency.
message Money {
string currency = 1;
string value = 2; // double amount in the string format.
}
Money = {
"currency": "EUR",
"value": "92.21"
}
Bank Accounts
The BankAccount
structure describes Bank accounts.
When TokenOS requests that the bank act upon some account,
it specifies that account with a BankAccount
.
The TransferEndpoint
structure has a BankAccount
and some customer
information (e.g., name, address). When proposing a
money transfer, TokenOS uses a TransferEndpoint
.
When linking a bank account to Token (described below), the account’s Account Features tell TokenOS what the account can do.
BankAccount.newBuilder() // set one of the account types (in this example, SWIFT): .setSwift(Swift.newBuilder().setBic(bic).setAccount(accountNumber)) .setAccountFeatures( AccountProtos.AccountFeatures.newBuilder() .setSupportsInformation(true) // AccountService working .setSupportsPayment(true)) // Can initiate transfers ... optionally set metadata ... .build()
message BankAccount {
// Token account Destination. Useful as source or destination
// for a transfer; doesn't make sense for a bank to "link" this.
message Token {
string member_id = 1;
string account_id = 2;
}
message Iban {
option (io.token.proto.extensions.message.redact) = true;
string bic = 1; // Optional
string iban = 2;
}
message Domestic {
option (io.token.proto.extensions.message.redact) = true;
string bank_code = 1;
string account_number = 2;
string country = 3; // 2-letter ISO 3166-1 alpha-2 country code
}
// Custom authorization
message Custom {
option (io.token.proto.extensions.message.redact) = true;
string bank_id = 1;
string payload = 2;
}
// Source account for guest checkout flow
message Guest {
string bank_id = 1;
string nonce = 2; // optional
}
// DEPRECATED ACCOUNT TYPES
// Source account managed by a co-opt bank
message Bank {
option deprecated = true; // Use Guest instead
string bank_id = 1;
}
// Deprecated; unused
message TokenAuthorization {
option deprecated = true;
io.token.proto.banklink.BankAuthorization authorization = 1;
}
message Sepa {
option deprecated = true; // Use Iban
option (io.token.proto.extensions.message.redact) = true;
string iban = 1; // International Bank Account Number
string bic = 2; // Bic code. Optional, except for non EEA countries
}
message Ach {
option deprecated = true; // Use Domestic (US)
option (io.token.proto.extensions.message.redact) = true;
string routing = 1; // Routing number
string account = 2;
}
message FasterPayments {
option deprecated = true; // Use Domestic (UK)
option (io.token.proto.extensions.message.redact) = true;
string sort_code = 1;
string account_number = 2;
}
message Swift {
option deprecated = true;
option (io.token.proto.extensions.message.redact) = true;
string bic = 1; // BIC code AAAABBCCDD
string account = 2;
}
oneof account {
Token token = 1;
TokenAuthorization token_authorization = 2 [deprecated = true];
Swift swift = 3 [deprecated = true];
Sepa sepa = 4 [deprecated = true];
Ach ach = 5 [deprecated = true];
Bank bank = 6 [deprecated = true];
FasterPayments faster_payments = 9 [deprecated = true];
Custom custom = 10;
Guest guest = 11 [deprecated = true]; // use TransferEndpoint::bank_id instead
Iban iban = 12;
Domestic domestic = 13;
}
map<string, string> metadata = 7 [(io.token.proto.extensions.field.redact) = true];
AccountFeatures account_features = 8;
}
message AccountFeatures {
bool supports_payment = 1 [deprecated = true]; // use supports_send_payment and supports_receive_payment instead
bool supports_information = 2; // can get info, e.g., get balance
bool requires_external_auth = 3 [deprecated = true];
bool supports_send_payment = 4; // can send payments from account
bool supports_receive_payment = 5; // can receive payments to account
}
message TransferEndpoint {
io.token.proto.common.account.BankAccount account = 1 [deprecated = true]; // Account identifier, e.g., SWIFT transfer info
CustomerData customer_data = 2; // Customer data: name and address
string bank_id = 3;
io.token.proto.common.account.AccountIdentifier account_identifier = 4;
}
message CustomerData {
option (io.token.proto.extensions.message.redact) = true;
repeated string legal_names = 1; // Repeated in case of joint account holders.
io.token.proto.common.address.Address address = 2; // Physical address
}
// Bank Account representation.
// There is more than one way to refer to a bank account;
// this structure's contents depend on the chosen reference.
{
"token": {
"memberId": "m:2xIIp:5mXgKRNd",
"accountId": "a:7LAe9:5mXpRAcI"
}
}
{
"tokenAuthorization": {
"authorization": {
"bankId": "iron",
"accounts": [{
"ciphertext": "CBiZSBlbmNpcGhlcmVkIGF1dGggZGF0YQo="
}]
}
}
}
{
"swift": {
"bic": "IRONUSCA000",
"account": "iban:3814827"
}
}
{
"sepa": {
"iban": "IE64BOFI90583812345678"
}
}
{
"ach": {
"routing": "067014822",
"account": "2589016585911"
}
}
Transactions
A Transaction
object represents a transaction (debit or credit) of an account. If the transaction is a Token transaction, it contains the ID of the corresponding token and the transfer ID. TransactionStatus
represents which stage a given transaction has reached. See Transfer Service.
enum TransactionType {
INVALID_TYPE = 0;
DEBIT = 1;
CREDIT = 2;
}
enum TransactionStatus {
INVALID_STATUS = 0; // invalid status
PENDING = 1; // the transaction is pending submission
PROCESSING = 7; // the transaction is being processed
SUCCESS = 2; // the transaction has been successful
PENDING_EXTERNAL_AUTHORIZATION = 15; // the transaction requires authorization by the user to complete
FAILURE_CANCELED = 10; // the transaction has been canceled, rolled back
FAILURE_INSUFFICIENT_FUNDS = 3; // the transaction has failed due to insufficient funds
FAILURE_INVALID_CURRENCY = 4; // the transaction has failed due to currency mismatch
FAILURE_PERMISSION_DENIED = 6; // the transaction has failed due to access violation
FAILURE_QUOTE_EXPIRED = 11; // the transaction has failed because the quote has expired
FAILURE_INVALID_AMOUNT = 12; // the transaction has failed due to invalid amount
FAILURE_INVALID_QUOTE = 13; // the transaction has failed due to invalid quote (wrong fx rate)
FAILURE_EXPIRED = 14; // the transaction has failed to complete within allotted time
FAILURE_DECLINED = 18; // the transaction has failed, because bank has declined
FAILURE_GENERIC = 5; // the transaction has failed due to other reasons
SENT = 16; // the transaction has been sent but has not been acknowledged by the bank
INITIATED = 17; // the transaction has been initiated but the result is unknown, this may be the final status and may not get updated later
STATUS_NOT_AVAILABLE = 19; // the transaction status is not available
}
message Transaction {
string id = 1; // Transaction ID.
TransactionType type = 2; // Debit or credit
TransactionStatus status = 3; // Status. For example, SUCCESS or FAILURE_CANCELED
io.token.proto.common.money.Money amount = 4; // Transaction amount.
string description = 5 [(io.token.proto.extensions.field.redact) = true]; // Optional description
string token_id = 6; // Points to the token, only set for Token transactions.
string token_transfer_id = 7; // Points to the token transfer, only set for Token transactions.
int64 created_at_ms = 8; // Creation time
map<string, string> metadata = 9 [(io.token.proto.extensions.field.redact) = true]; // Additional fields. Optional.
providerspecific.ProviderTransactionDetails provider_transaction_details = 10;
io.token.proto.common.transferinstructions.TransferEndpoint creditor_endpoint = 11;
}
message Balance {
string account_id = 1;
io.token.proto.common.money.Money current = 2;
io.token.proto.common.money.Money available = 3;
int64 updated_at_ms = 4;
repeated TypedBalance other_balances = 5; // optional
message TypedBalance {
string type = 1;
io.token.proto.common.money.Money amount = 2;
int64 updated_at_ms = 3;
}
}
enum RequestStatus {
INVALID_REQUEST = 0;
SUCCESSFUL_REQUEST = 1; // success
MORE_SIGNATURES_NEEDED = 2; // failed, needed to use a PRIVILEGED key
}
// Represents a standing order as defined by the bank.
message StandingOrder {
string id = 1; // Standing order ID defined by the bank
Status status = 2;
string token_id = 3; // Points to the token, only set for Token standing orders.
string token_submission_id = 4; // Points to the token submission, only set for Token standing orders.
int64 created_at_ms = 5; // CreationTime
providerspecific.ProviderStandingOrderDetails provider_standing_order_details = 6;
string frequency = 7; // ISO 20022: DAIL, WEEK, TOWK, MNTH, TOMN, QUTR, SEMI, YEAR
// If empty, frequency is specified in ProviderStandingOrderDetails
io.token.proto.common.transferinstructions.TransferEndpoint creditor_endpoint = 8;
enum Status {
INVALID = 0;
ACTIVE = 1;
INACTIVE = 2;
PROCESSING = 3;
FAILED = 4;
UNKNOWN = 5; // unable to retrieve updated status from the bank
}
}
Transaction = {
"id": "dd29e7b6ab953d5b3fb44c1dd9ac7efd",
"type": "DEBIT",
"status": "PROCESSING",
"amount": {
"currency": "EUR",
"value": "12.34"
},
"description": "Sample transaction",
"tokenId": "tt:gOkIOWYHK:5mXpRAcI",
"tokenTransferId": "t:UpoJIJbvdOKXQWka:5mXpRAcI"
}
// Transaction Status
INVALID_STATUS
PENDING
PROCESSING
SUCCESS
FAILURE_INSUFFICIENT_FUNDS
FAILURE_INVALID_CURRENCY
FAILURE_PERMISSION_DENIED
FAILURE_GENERIC
Account Balance
A Balance
Java object represents an account’s balance.
package io.token.sdk.api;
import com.google.auto.value.AutoValue;
import io.token.proto.common.transaction.TransactionProtos.Balance.TypedBalance;
import java.math.BigDecimal;
import java.util.List;
/**
* Represents account balance.
*/
@AutoValue
public abstract class Balance {
/**
* Creates new balance instance.
*
* @param currency currency
* @param available available balance
* @param current current balance
* @param updatedAtMs date of last update
* @param otherBalances other optional typed balances
* @return new balance instance
*/
public static Balance create(
String currency,
BigDecimal available,
BigDecimal current,
long updatedAtMs,
List<TypedBalance> otherBalances) {
return new AutoValue_Balance(currency, available, current, updatedAtMs, otherBalances);
}
/**
* Returns account currency.
*
* @return account currency
*/
public abstract String getCurrency();
/**
* Returns available balance.
*
* @return available balance
*/
public abstract BigDecimal getAvailable();
/**
* Returns current balance.
*
* @return current balance
*/
public abstract BigDecimal getCurrent();
/**
* Returns date of last update.
*
* @return date of last update
*/
public abstract long getUpdatedAtMs();
/**
* Returns list of other optional typed balances.
*
* @return list of other balances
*/
public abstract List<TypedBalance> getOtherBalances();
}
Account Linking Service
TokenOS uses AccountLinkingService
to get information
about bank accounts a user wishes to link with a Member.
A user has navigated the bank’s
account linking web flow
to authenticate as a bank customer and choose accounts to link.
The user’s Token app received a bearer token string from that flow
and passed that to TokenOS.
TokenOS calls the AccountLinkingService
getBankAuthorization
method to get information about the chosen bank accounts:
currency, supported features, etc.
This uses the API:
public interface AccountLinkingService {
/**
* Exchanges OAuth Implicit Grant access token for bank authorization payload.
*
* @param accessToken OAuth access token
* @return bank authorization payload
*/
BankAuthorization getBankAuthorization(String accessToken);
}
BankAccount Structure
A BankAccount
structure
describes a bank account.
When linking an account, the bank uses a BankAccount
structure.
Later, when TokenOS refers to this account in requests, it will
use the BankAccount
value that the bank provided.
This structure has fields:
account
: Account information useful for transfers. E.g., this can be SEPA transfer information.account_features
: Flags TokenOS uses to know which features the account supports.metadata
: string:string map.
Here, account
is a “destination” of some inter-bank transfer method.
A bank might support more than one such transfer method.
When linking a bank account to TokenOS, an implementor might wonder:
If we use SEPA information to link to TokenOS, how will TokenOS
know this account accepts transfers via both SEPA and SWIFT?
This happens via the resolveTransferDestination
method, described later.
message BankAccount {
// Token account Destination. Useful as source or destination
// for a transfer; doesn't make sense for a bank to "link" this.
message Token {
string member_id = 1;
string account_id = 2;
}
message Iban {
option (io.token.proto.extensions.message.redact) = true;
string bic = 1; // Optional
string iban = 2;
}
message Domestic {
option (io.token.proto.extensions.message.redact) = true;
string bank_code = 1;
string account_number = 2;
string country = 3; // 2-letter ISO 3166-1 alpha-2 country code
}
// Custom authorization
message Custom {
option (io.token.proto.extensions.message.redact) = true;
string bank_id = 1;
string payload = 2;
}
// Source account for guest checkout flow
message Guest {
string bank_id = 1;
string nonce = 2; // optional
}
// DEPRECATED ACCOUNT TYPES
// Source account managed by a co-opt bank
message Bank {
option deprecated = true; // Use Guest instead
string bank_id = 1;
}
// Deprecated; unused
message TokenAuthorization {
option deprecated = true;
io.token.proto.banklink.BankAuthorization authorization = 1;
}
message Sepa {
option deprecated = true; // Use Iban
option (io.token.proto.extensions.message.redact) = true;
string iban = 1; // International Bank Account Number
string bic = 2; // Bic code. Optional, except for non EEA countries
}
message Ach {
option deprecated = true; // Use Domestic (US)
option (io.token.proto.extensions.message.redact) = true;
string routing = 1; // Routing number
string account = 2;
}
message FasterPayments {
option deprecated = true; // Use Domestic (UK)
option (io.token.proto.extensions.message.redact) = true;
string sort_code = 1;
string account_number = 2;
}
message Swift {
option deprecated = true;
option (io.token.proto.extensions.message.redact) = true;
string bic = 1; // BIC code AAAABBCCDD
string account = 2;
}
oneof account {
Token token = 1;
TokenAuthorization token_authorization = 2 [deprecated = true];
Swift swift = 3 [deprecated = true];
Sepa sepa = 4 [deprecated = true];
Ach ach = 5 [deprecated = true];
Bank bank = 6 [deprecated = true];
FasterPayments faster_payments = 9 [deprecated = true];
Custom custom = 10;
Guest guest = 11 [deprecated = true]; // use TransferEndpoint::bank_id instead
Iban iban = 12;
Domestic domestic = 13;
}
map<string, string> metadata = 7 [(io.token.proto.extensions.field.redact) = true];
AccountFeatures account_features = 8;
}
message AccountFeatures {
bool supports_payment = 1 [deprecated = true]; // use supports_send_payment and supports_receive_payment instead
bool supports_information = 2; // can get info, e.g., get balance
bool requires_external_auth = 3 [deprecated = true];
bool supports_send_payment = 4; // can send payments from account
bool supports_receive_payment = 5; // can receive payments to account
}
BankAuthorization structure
This structure contains information that links a member
to BankAccount
structures. The bank cryptographically
signs these.
- The Bank’s ID.
- For each account to link:
BankAccount
structure by which TokenOS should refer to this account later- A “pretty” account name, shown to the user.
- The value of the
memberId
URL parameter mentioned before. - An expiration, in ms since Jan 1, 1970.
The BankAccountAuthorizer class eases creation and signing.
message BankAuthorization {
string bank_id = 1; // Bank ID, e.g., "iron"
repeated io.token.proto.common.security.SealedMessage accounts = 2; // Encrypted link info
}
Here, the SealedMessage “seals” a PlaintextBankAuthorization
message PlaintextBankAuthorization {
string member_id = 1; // Token member id
string account_name = 2 [(io.token.proto.extensions.field.redact) = true]; // e.g., "Checking account with # ending 5678"
BankAccount account = 3 [(io.token.proto.extensions.field.redact) = true]; // Account info by some method, e.g., SEPA
int64 expiration_ms = 4; // Expiration timestamp in ms
}
Account Service
TokenOS uses AccountService
methods to ask a bank for information about
the bank’s customers and customer accounts.
Account Balance
TokenOS queries the bank for an account’s current balance, current and available. It might do this, for example, when the user lists their accounts in the Token client application.
This uses the API:
Balance getBalance(BankAccount account);
Transaction History
TokenOS queries the bank for an account’s recent transactions
(getTransactions
) or information about one transaction
(getTransaction
).
The getTransactions
method is a “paged” query. It should only return up to limit
transactions, starting from the most recent. It also returns offset
, which marks
the place in the full transaction history. The list of transactions together with the
offset constitute a PagedList
. For example, the offset could be the ID of the last
transaction returned. Then, when TokenOS calls getTransactions
with that transaction
ID as the offset
parameter, the implementation should skip that transaction
as well as every transaction more recent than it. The offset parameter does not need
to be comprehensible–only the bank needs to understand it.
TokenOS might call getTransactions
because a customer views their transaction history
in the Token mobile app. If, say, the user scrolls to the bottom of their transaction
history, then TokenOS might make a series of paged getTransactions
calls.
You can now filter transactions using the startDate
and endDate
parameters. startDate
is an optional inclusive lower bound of the transaction booking date and endDate
is an optional inclusive upper bound of the transaction booking date. Each new page of transactions will use the same date range specified in the original request. (Currently this feature is only supported in Java. It will soon be available in other languages.)
This uses the APIs:
default PagedList<Transaction, String> getTransactions(
BankAccount account,
String offset,
int limit,
Optional<Transaction> getTransaction(BankAccount account, String transactionId);
message Transaction {
string id = 1; // Transaction ID.
TransactionType type = 2; // Debit or credit
TransactionStatus status = 3; // Status. For example, SUCCESS or FAILURE_CANCELED
io.token.proto.common.money.Money amount = 4; // Transaction amount.
string description = 5 [(io.token.proto.extensions.field.redact) = true]; // Optional description
string token_id = 6; // Points to the token, only set for Token transactions.
string token_transfer_id = 7; // Points to the token transfer, only set for Token transactions.
int64 created_at_ms = 8; // Creation time
map<string, string> metadata = 9 [(io.token.proto.extensions.field.redact) = true]; // Additional fields. Optional.
providerspecific.ProviderTransactionDetails provider_transaction_details = 10;
io.token.proto.common.transferinstructions.TransferEndpoint creditor_endpoint = 11;
}
Transfer Destination Choices
A bank might support more than one way of transferring funds; e.g., a bank might support both SEPA and SWIFT. When TokenOS “plans” a transfer, it asks the destination bank (the “credit” side) for a list of choices. TokenOS then gives this list of choices to the source bank (the “debit” side). The source bank can choose a transfer method.
When TokenOS calls this method, it passes in the BankAccount
the bank
used when linking the account.
This uses the APIs:
default List<TransferEndpoint> resolveTransferDestination(BankAccount account) {
throw new StatusRuntimeException(UNIMPLEMENTED);
Standing Order
The getStandingOrder
method is called using the standingOrderId
and accountId
associated with the standing order. This method returns a standing order associated with that ID.
Like with getTransactions
, the getStandingOrders
method is a “paged” query, returning a list of standing orders for the account and an offset
. The offset is there to indicate where the page left off in the history of standing orders. Together, this constitutes a PagedList
.
For example, the offset could be the ID of the last standing order returned. When TokenOS calls getStandingOrders
with that standing order ID as the offset
parameter, the implementation will skip that standing order and every more recent standing order.
NOTE: The offset parameter does not need to be comprehensible to anyone other than the bank.
Transfer Service
The TransferService
is implemented to handle Transfer Requests. It has one required method - the createTransfer
method, which takes the transferRequest
object parameter.
Token makes a
transferRequest
and within this request is atransferId
.Return a
transferResult
, including thetransactionId
,status
, andsource
(this optional field contains theaccount
(such asiban
) and account holder name). See theaccount
proto for more information.
IMPORTANT: the transferId
and the transactionId
are NOT the same.
In the getTransaction
response, the SAME transaction ID is returned. There is only one transaction ID per transaction.
If the bank is unable to provide a transaction ID, the transferId
(created and owned by Token), must be persisted and bound to the transaction. Token can then call getTransferStatus
for updates to the status. This method is required when the bank is not able to return the final transaction ID in the transferResult
AND when the bank is implementing a Guest Checkout Flow.
To find out which account is debited, call the getAccount
method on thetransferRequest
object.
It will return the BankAccount
structure that linked
the account to Token.
The following paragraph and instructions contain information that has been DEPRECATED as of version 2.3.4 of the Token SDK. See the above method for the latest transfer service method.
The TransferService
handles transfer requests. It has one required method-the transfer
method-that takes the transfer
object parameter. Token requests the bank to transfer funds from one account to another by means of SEPA, SWIFT, FPS, etc. only invoking the Transfer Service of the bank from which the payment is being made.
Token makes a
TransferRequest
to the bank. Within this request is atransferId
.The bank returns the
transactionID
and the status of the transaction.
Future-Dated Payments
The received transfer request may include a populated execution_date
field, indicating a future-dated payment request. This will include a date on which the payment is to take place.
Standing Orders
The standing order request handles recurring payments. To support this, banks must implement the createStandingOrder
endpoint. When this method is called, banks must return a standing order ID. Token will use this ID to retrieve the existing standing order, which must be returned by the bank.
IMPORTANT: It is the bank’s responsibility to initiate each individual payment. Token will NOT prompt the bank for payment each time it is to occur.
The amount of each payment is fixed.
Bulk Payment
The BulkTransferRequest
handles bulk transfers taken from a single consent.
To support bulk transfers, the bank needs to implement the createBulkTransfer
endpoint. When Token calls createBulkTransfer
, the bank initiates all the transactions specified in the bulk transfer request and returns a bulkTransferId
and a list of BulkTransaction
objects with their individual transactionId
(s).
Token can then call getBulkTransfer
with the bulkTransferId
and retrieve the status of each transaction within the transfer.
Fields of the Bulk Transfer Request, set by the initiator of the token.
token_bulk_transfer_id |
required A transfer ID set by Token that references the bulk transfer |
ref_id |
optional A reference ID set by the initiator of the token request |
token_initiator_id |
required The member ID of the initiator of the token request |
total_amount |
required The total amount of the transfers, regardless of currency; used for redundancy checks |
transfers |
required The list of transfers in the bulk transfer |
source_account |
required The source account of the payment. Identifies the type of payment (example: Swift; SEPA) |
description |
optional A string describing what the payment is for |
The bulk transfer request below is for reference-use ONLY:
Note there are two source
accounts: The bank source_account
is the account returned in the getBankAuthorization
method call. The “token” account within the payload is Token’s internal representation of the bank source_account
.
Bulk Transfer Request (JSON)
{
"token_bulk_transfer_id": "bt:CYgigCcLqioSuw13xSuWbzN2bTqkFcwK47mSrgCv2WXL:5zKcENpV",
"ref_id": "WL1RKwkqQ5SvsWeV",
"token_initiator_id": "m:VEjfa2mKFaY9at5GZAoaU3mFRyr:5zKtXEAq",
"payload": {
"transfers": [
{
"amount": "20",
"currency": "EUR",
"refId": "NJbaqnrOFtedUjQ",
"description": "books",
"destination": {
"sepa": {
"bic": "SLVRDEFR000",
"iban": "DE123456789"
}
}
},
{
"amount": "50",
"currency": "GBP",
"refId": "JAwssYkmeKutSVB",
"description": "bookends",
"destination": {
"sepa": {
"bic": "GOLDUKLN000",
"iban": "UK345678910"
}
}
},
{
"amount": "1750.0",
"currency": "GBP",
"refId": "PxDZYznlYXFYUYi",
"description": "iron throne",
"destination": {
"sepa": {
"bic": "IRONUKCA000",
"iban": "UK678910123"
}
}
}
],
"total_amount": "1820.0",
"source": {
"account": {
"token": {
"member_id": "m:VEjfa2mKFaY9at5GZAoaU3mFRyr:5zKtXEAq",
"account_id": "a:FRpTbL5RpvD6t1CBW6bcM2DKnhMCLdV16LrysskRhHZf:5zKcENpV"
}
}
}
},
"source_account": {
"account": {
"sepa": {
"bic": "IRONUKCA000",
"account": "15822196688826391062"
}
}
}
}
Bulk Payment Javadocs
Bulk Payment Protobuf
Relevant APIs for the Transfer Service:
TransferService.transfer
: methodTransferService.getTransferStatus
(mandatory when implementing a guest checkout flow): method that returns the status of a transaction
Transfer Service
/**
* Creates a transaction for the specified transfer request.
*
* @param transferRequest the token transfer request
* @return instance of TransferResult
*/
default TransferResult createTransfer(TransferRequest transferRequest) {
try {
Transfer.Builder builder = Transfer
.newBuilder()
.setTokenTransferId(transferRequest.getTokenTransferId())
.setTransferInstructions(transferRequest.getTransferInstructions())
.setTransferRefId(transferRequest.getTransferRefId())
.setConfirmFunds(transferRequest.getConfirmFunds())
.setDescription(transferRequest.getDescription())
.setRequestedAmount(transferRequest.getRequestedAmount())
.setRequestedAmountCurrency(transferRequest.getRequestedAmountCurrency())
.setTokenInitiatorId(transferRequest.getTokenInitiatorId())
.setTokenRefId(transferRequest.getTokenRefId())
.setTransactionAmount(transferRequest.getTransactionAmount())
.setTransactionAmountCurrency(transferRequest.getTransactionAmountCurrency());
transferRequest.getExecutionDate().ifPresent(builder::setExecutionDate);
transferRequest.getRemittanceReference().ifPresent(builder::setRemittanceReference);
return TransferResult.create(transfer(builder.build()), SUCCESS);
} catch (TransferException ex) {
Notification Service
Token notifies members of important events. For example, if a merchant uses Token to
ask a member for a payment, Token notifies the member to ask for approval.
A business using the
Token SDK
can register to deliver these notifications. We say it “subscribes” to notifications.
Thus, if a Bank has some way to deliver notifications to a Token member, it can register
to do so by subscribing to the member’s notifications.
TokenOS uses the Bank Integration SDK’s NotificationService
to push these
notifications to the Bank. The Bank can then deliver these notifications to the member.
If a bank does not subscribe to notifications, then Token will not normally call the Bank’s
NotificationService
and it is fine if the Bank’s implementation does nothing.
The bank’s NotificationService
has one method, notify
. There are two
parameters: a Subscriber
with the information needed to deliver the notification; and a
Notification
with the notification’s content.
Calling subscriber.getHandlerInstructionsMap()
gets the
(optional) key-value pairs set when subscribing
to the member’s notifications. Thus if the Bank subscribed to a member with handler instructions
{"bankAccountNumber":"CH1234567"}
, the Bank’s notify
method might call
subscriber.getHandlerInstructionsMap().get("bankAccountNumber")
and contact the member accordingly.
This uses the API:
public interface NotificationService {
void notify(Notification notification, Subscriber subscriber);
}
Onboarding
As a bank integrates with TokenOS, it needs to provide (and get) some information. If you’re setting up integration, you might not have all of this information ready at first; please provide it as you set it up.
Information Banks Provide
Host DNS name and port number for Token to communicate to. Provide this information for both your test system and live system.
X.509 certificate chain file in PEM format. This bank server certificate must match the bank’s DNS. Together with Token’s certificates, this enables TLS communication.
The bank’s name.
The bank’s URL, email address, and telephone number to indicate the bank’s preferred means of communication with Token.
The URL of the bank’s account-linking web interface; configuration for encrypting bank account link authorization.
Public key corresponding to the private key the bank uses to sign account-linking authorizations. Token uses this public key to check signatures.
(This list last updated 2017-12-19)
Information Token Provides
X.509 certificate in PEM format. This is the client certificate Token will use when comunicating with the bank server. Together with the Bank’s certificates, this enables TLS communication. When you configure your server, you will tell it to use this certificate.
IP addresses If you’d like to limit IP addresses, Token can provide the addresses from which its requests are sent.
Establishing TLS (SSL)
The communication between Token Bank Integration SDK and Token Cloud is secured by means of a mutual TLS authentication. As a part of bank onboarding process, Token provides an X.509 certificate the Bank’s server needs to trust. Likewise, Token requires an X.509 certificate from the Bank to establish mutual trust.
The sample implementation has scripts to generate certificates and code to use those certificates when communicating with the Token Cloud.
Once you have set up certificates and keys, see Server Builder to configure your server to use them.
Troubleshooting the Sample Bank
Jetty ALPN/NPN has not been properly configured.
Despite the wording of this gRPC error message, the sample bank doesn’t use Jetty.
It uses
netty’s Tomcat Native, netty-tcnative-boringssl-static.
See its documentation for troubleshooting tips;
e.g., on a Mac that doesn’t already have openssl
installed, install openssl
.
Testing TLS, Certs with openssl
When Token’s servers and a bank server “talk” with each other, they use TLS. The bank server has certificates from Token; Token servers have a certificate from the bank. Using these, the servers can communicate securely.
You can use the openssl
tool to make sure that
certificates are set up correctly.
You’re trying to determine: if Token has these
certificates from us, can their servers talk to ours?
To try this out:
# first go to config/tls directory in bank-sample-java project $ cd ./config/tls # If you haven't already, edit regenerate-certs # and replace "demo.example.com" with your domain name $ vim regenerate-certs # If you haven't already, generate certificates for your # domain: $ ./regenerate-certs # you will get new “cert.pem” and “key.pem” # Set up a directory for our client (a.k.a. “pretend token”) certificates # (You can use whatever directory you like.) $ mkdir ../tmp; cd ../tmp # run regenerate-certs to generate your client certificate # (you should see “cert.pem” and “key.pem” in your current directory) $ ../tls/regenerate-certs pretend.io # rename them for readability $ mv cert.pem cert-client.pem ; mv key.pem key-client.pem $ cat ../tls/cert.pem >> trusted-certs-client.pem # client, trust bank cert $ cat ./cert-client.pem >> ../tls/trusted-certs.pem # bank, trust client cert $ ...Deploy the sample server... $ ...Remember to pass the --ssl command-line parameter... # Now check the connection with openssl from the directory # with client certificate: $ openssl s_client -connect your domain name:your port number -CAfile trusted-certs-client.pem -cert cert-client.pem -key key-client.pem
The simple openssl
s_client
client program prints diagnostic information.
Hopefully it says many things and finishes with Verify return code: 0 (ok)
closed
. If it doesn’t, it should print out something that helps track
down the problem.
API
This is the network part of the Bank Integration API, the protocols by which TokenOS communicates with a Bank Server. It is shown in gRPC protocol using protobuf to define and encode the messages. This network API is similar to, but not quite the same as, the API a bank implements. The Token Bank Integration SDK comes with some code that eases integration tasks. This code converts some data from protocol buffers to simpler structures.
Account API
Gets basic account information.
Get Transaction
Get information about one transaction.
message GetTransactionRequest {
string transaction_id = 1;
io.token.proto.common.account.BankAccount account = 2;
string consent_id = 3;
}
message GetTransactionResponse {
io.token.proto.common.transaction.Transaction transaction = 1;
}
In AccountService …
rpc GetTransaction (GetTransactionRequest) returns (GetTransactionResponse) {
option (google.api.http) = {
get: "/accounts/transactions?transaction_id={transaction_id}&account.swift.bic={account.swift.bic}&account.swift.account={account.swift.account}&account.sepa.iban={account.sepa.iban}&account.sepa.bic={account.sepa.bic}&account.ach.routing={account.ach.routing}&account.ach.account={account.ach.account}&account.faster_payments.sort_code={account.faster_payments.sort_code}&account.faster_payments.account_number={account.faster_payments.account_number}&account.custom.bank_id={account.custom.bank_id}&account.custom.payload={account.custom.payload}"
};
option (io.token.proto.extensions.method.kafka_topic) = "metric_bank_client_rpc_call";
}
GET '/transactions/{transaction_id}'
response = {
"transaction": {
"id": "dd29e7b6ab953d5b3fb44c1dd9ac7efd",
"type": "DEBIT",
"status": "PROCESSING",
"amount": {
"currency": "EUR",
"value": "12.34"
},
"description": "Sample transaction",
"tokenId": "tt:gOkIOWYHK:5mXpRAcI",
"tokenTransferId": "t:UpoJIJbvdOKXQWka:5mXpRAcI",
"createdAtMs": "1499301354256"
}
}
Get Transactions
Gets transactions of an account. This can be limited only to accounts that have been enabled with Token. Banks can define whatever accountId format they want. (“checking1”, IBAN, or a proprietary format). Note that transactions are different than transfers.
message GetTransactionsRequest {
io.token.proto.common.account.BankAccount account = 1;
int32 integer_offset = 2 [deprecated=true]; // use offset instead
int32 limit = 3;
string offset = 4;
// Optional lower bound for a transaction's booking date as returned by the bank, in the format 'YYYY-MM-DD' (e.g. '2016-01-01').
// If specified, then only transactions whose bank booking date is equal to or later than the given date will be regarded.
string start_date = 5;
// Optional upper bound for a transaction's booking date as returned by the bank (= original booking date), in the format 'YYYY-MM-DD' (e.g. '2016-01-01').
// If specified, then only transactions whose bank booking date is equal to or earlier than the given date will be regarded.
string end_date = 6;
string consent_id = 7;
}
message GetTransactionsResponse {
repeated io.token.proto.common.transaction.Transaction transactions = 1;
string offset = 2;
}
In AccountService …
rpc GetTransactions (GetTransactionsRequest) returns (GetTransactionsResponse) {
option (google.api.http) = {
get: "/accounts/transactions?offset={offset}&limit={limit}&account.swift.bic={account.swift.bic}&account.swift.account={account.swift.account}&account.sepa.iban={account.sepa.iban}&account.sepa.bic={account.sepa.bic}&account.ach.routing={account.ach.routing}&account.ach.account={account.ach.account}&account.faster_payments.sort_code={account.faster_payments.sort_code}&account.faster_payments.account_number={account.faster_payments.account_number}&account.custom.bank_id={account.custom.bank_id}&account.custom.payload={account.custom.payload}"
};
option (io.token.proto.extensions.method.kafka_topic) = "metric_bank_client_rpc_call";
}
GET "/accounts/{account.bic}/{account.account}/transactions?offset={offset}&limit={limit}"
response = {
"transactions": [{
"id": "dd29e7b6ab953d5b3fb44c1dd9ac7efd",
"type": "DEBIT",
"status": "PROCESSING",
"amount": {
"currency": "EUR",
"value": "12.34"
},
"description": "Sample transaction",
"tokenId": "tt:gOkIOWYHK:5mXpRAcI",
"tokenTransferId": "t:UpoJIJbvdOKXQWka:5mXpRAcI",
"createdAtMs": "1499301354256"
}]
}
Get Balance
Fetches balance of an account
message GetBalanceRequest {
io.token.proto.common.account.BankAccount account = 1;
string consent_id = 2;
}
message GetBalanceResponse {
io.token.proto.common.money.Money current = 1;
io.token.proto.common.money.Money available = 2;
int64 updated_at_ms = 3;
repeated io.token.proto.common.transaction.Balance.TypedBalance other_balances = 4; // optional
}
In AccountService …
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {
option (google.api.http) = {
get: "/accounts/balance?account.swift.bic={account.swift.bic}&account.swift.account={account.swift.account}&account.sepa.iban={account.sepa.iban}&account.sepa.bic={account.sepa.bic}&account.ach.routing={account.ach.routing}&account.ach.account={account.ach.account}&account.faster_payments.sort_code={account.faster_payments.sort_code}&account.faster_payments.account_number={account.faster_payments.account_number}&account.custom.bank_id={account.custom.bank_id}&account.custom.payload={account.custom.payload}"
};
option (io.token.proto.extensions.method.kafka_topic) = "metric_bank_client_rpc_call";
}
GET '/accounts/{account.bic}/{account.account}/balance'
response = {
"current": {
"currency": "EUR",
"value": "123.45"
},
"available": {
"currency": "EUR",
"value": "123.45"
}
}
Transfer API
Transfers money from one account to another, from a source account at the bank, and returns a transaction. This call is used for internal transfers (intra-bank) as well as push transactions (SEPA credit, FPS), as defined by the “method”.
message TransferRequest {
string transfer_id = 1;
io.token.proto.common.money.Money requested_amount = 2 [deprecated = true];
io.token.proto.common.money.Money transaction_amount = 3;
io.token.proto.common.account.BankAccount source = 4 [deprecated=true];
repeated io.token.proto.common.transferinstructions.TransferEndpoint destinations = 5 [deprecated=true];
string description = 9; // optional transfer description
string token_ref_id = 10; // reference ID of the token, set by the initiator
string transfer_ref_id = 11;
io.token.proto.common.transferinstructions.TransferInstructions.Metadata metadata = 12 [deprecated=true];
string token_initiator_id = 13; // ID of member who requested token creation
repeated io.token.proto.common.transferinstructions.TransferDestination transfer_destinations = 14 [deprecated=true];
string execution_date = 15; // execution date of the transfer
bool confirm_funds = 16;
io.token.proto.common.transferinstructions.TransferInstructions transfer_instructions = 17;
string consent_id = 18;
string remittance_reference = 19 [(io.token.proto.extensions.field.hash) = true];
}
message TransferResponse {
string transaction_id = 1;
StatusCode status = 2;
string status_description = 3;
io.token.proto.common.transferinstructions.TransferEndpoint source = 4;
io.token.proto.common.providerspecific.ProviderTransferDetails provider_details = 5; // optional if there are provider details
}
In TransferService …
rpc Transfer (TransferRequest) returns (TransferResponse) {
option (google.api.http) = {
post: "/transfers"
body: "*"
};
option (io.token.proto.extensions.method.kafka_topic) = "metric_bank_client_rpc_call";
}
POST '/transfers'
body = {
"transferId": "t:I58bxf:5mXpRAcI",
"requestedAmount": {
"currency": "USD",
"value": "100.00"
},
"transactionAmount": {
"currency": "USD",
"value": "100.00"
},
"source": {
"swift": {
"bic": "IRONUSCA000",
"account": "iban:3814827"
}
},
"destinations": [{
"account": {
"swift": {
"bic": "IRONUSCA000",
"account": "iban:3814827"
}
},
"customerData": {
"legalNames": ["John Smith"],
"address": {
"houseNumber": "332C",
"street": "Cook St"
}
}
}],
"feeResponsibility": "SHARED_FEE",
"description": "Sample Transfer"
}
response = {
"transactionId": "458b10fd2a637fc289372baa428e8747",
"status": "PROCESSING",
"statusDescription": "PROCESSING"
}
Storage API
Some Banks store Token’s data about them. They do so via these protocols.
Set Value
Sets a value in a key value store. The key value store should map string keys to string values. This will be used for storing a small amount of bank related data. For example, the mapping between a Token accountId and an internal accountId.
message SetValueRequest {
enum ContentCategory {
INVALID = 0;
ACCOUNT_INFO = 1;
TOKEN_INFO = 2;
}
string key = 1;
bytes value = 2 [(io.token.proto.extensions.field.redact) = true];
ContentCategory category = 3;
}
message SetValueResponse {
bytes previous = 1 [(io.token.proto.extensions.field.redact) = true];
}
In StorageService …
rpc SetValue (SetValueRequest) returns (SetValueResponse) {
option (google.api.http) = {
put: "/storage/{key}"
};
}
PUT "/storage/{key}"
body = {
"key": "m:88Enz2:5zKtXEAq/a:UP4PwzxDuKbK:5zKcENpV",
"value": "Wm05MWNpQm1hWFpsSUhOcGVBbz0=",
"category": "ACCOUNT_INFO"
}
response = {
"previous": "YjI1bElIUjNieUIwYUhKbFpRbz0="
}
Get Value
Gets a value in the key value store. The key value store should map string keys to string values. This will be used for storing a small amount of bank related data. For example, the mapping between a Token accountId and an internal accountId.
message GetValueRequest {
string key = 1;
}
message GetValueResponse {
bytes value = 1 [(io.token.proto.extensions.field.redact) = true];
}
In StorageService …
rpc GetValue (GetValueRequest) returns (GetValueResponse) {
option (google.api.http) = {
get: "/storage/{key}"
};
}
GET "/storage/{key}"
response = {
"value": "Wm05MWNpQm1hWFpsSUhOcGVBbz0="
}
Remove Value
Removes a value from the key store.
message RemoveValueRequest {
string key = 1;
}
message RemoveValueResponse {
}
In StorageService …
rpc RemoveValue (RemoveValueRequest) returns (RemoveValueResponse) {
option (google.api.http) = {
delete: "/storage/{key}"
};
}
DELETE "/storage/{key}"
response = {}
Notification API
Some Banks register to transmit notifications to their customers. These Banks do so via this protocol.
Notify
Requests that the bank relay a notification to a subscriber. For more information, see Notification Service.
syntax = "proto3";
package io.token.proto.common.notification;
import "banklink.proto";
import "security.proto";
import "token.proto";
import "member.proto";
import "extensions/field.proto";
option java_outer_classname = "NotificationProtos";
option csharp_namespace = "Tokenio.Proto.Common.NotificationProtos";
// Metadata for a notification
message DeviceMetadata {
string application = 1; // Name of the application to add keys to (e.g. Token, Chrome)
string application_version = 2; // Application version (e.g. 2.0)
string device = 3; // Device the application resides on (e.g. Mac, iPhone X), to support multiple devices
double longitude = 4 [(io.token.proto.extensions.field.redact) = true]; // Longitude of the user's location to signal where the request is coming from
double latitude = 5 [(io.token.proto.extensions.field.redact) = true]; // Latitude of the user's location
}
// A notification to the payer that a transfer was successfully processed.
message PayerTransferProcessed {
string transfer_id = 1; // transfer ID
}
// A notification to the payee that a transfer was successfully processed.
message PayeeTransferProcessed {
string transfer_id = 1; // transfer ID
}
// A notification to the payer that a transfer failed.
message PayerTransferFailed {
string transfer_id = 1; // transfer ID
}
// A generic notification that a transfer was successfully processed.
message TransferProcessed {
string transfer_id = 1; // transfer ID
}
// A generic notification that a transfer failed.
message TransferFailed {
string transfer_id = 1; // transfer ID
}
// A notification that a bank wants to be linked.
message LinkAccounts {
io.token.proto.banklink.BankAuthorization bank_authorization = 1;
}
// A notification to step up / endorse a token.
// E.g., perhaps user tried to endorse in browser with only LOW-privilege
// key available but needs a HIGH-privilege key signature.
message StepUp {
string token_id = 1; // ID of Token to endorse
}
// A notification to step up a get balance(s) request
message BalanceStepUp {
repeated string account_id = 1; // Account IDs
}
// A notification to step up a transaction request
message TransactionStepUp {
string account_id = 1; // Account ID
string transaction_id = 2; // Transaction ID
}
// A notification to notify a member that a recovery process has completed
message RecoveryCompleted {}
// A notification that a key wants to be added to a member. Clients should timeout the notification
// and screen, once the expires_ms has passed
message AddKey {
int64 expires_ms = 3; // Expiration time
repeated io.token.proto.common.security.Key keys = 4; // List of new keys to add
DeviceMetadata device_metadata = 5;
}
// A notification that a bank wants to be linked, and keys want to be added.
message LinkAccountsAndAddKey {
LinkAccounts link_accounts = 1;
AddKey add_key = 2;
}
// A notification to request a payment
message PaymentRequest {
io.token.proto.common.token.TokenPayload payload = 1; // requested payment
}
// A notification that a token was cancelled
message TokenCancelled {
string token_id = 1; // Token ID
}
message EndorseAndAddKey {
option deprecated = true;
io.token.proto.common.token.TokenPayload payload = 1;
AddKey add_key = 2;
string token_request_id = 3; // Optional token request ID
string bank_id = 4; // Optional bank ID
string state = 5; // Optional token request state for signing
io.token.proto.common.member.ReceiptContact contact = 6; //Optional receipt contact
}
// A notification that a token needs to be created/endorsed
message CreateAndEndorseToken {
io.token.proto.common.token.TokenRequest token_request = 1;
AddKey add_key = 2; // Optional key to add
io.token.proto.common.member.ReceiptContact contact = 3; // Optional receipt contact
}
// A notification to indicate that a previously sent notification was invalidated
message NotificationInvalidated {
string previous_notification_id = 1;
}
// A data that goes in a NotifyRequest
message NotifyBody {
oneof body {
io.token.proto.common.notification.PayerTransferProcessed payer_transfer_processed = 1;
io.token.proto.common.notification.LinkAccounts link_accounts = 2;
io.token.proto.common.notification.StepUp step_up = 3;
io.token.proto.common.notification.AddKey add_key = 4;
io.token.proto.common.notification.LinkAccountsAndAddKey link_accounts_and_add_key = 5;
io.token.proto.common.notification.PayeeTransferProcessed payee_transfer_processed = 6;
io.token.proto.common.notification.PaymentRequest payment_request = 7;
io.token.proto.common.notification.PayerTransferFailed payer_transfer_failed = 8;
io.token.proto.common.notification.TransferProcessed transfer_processed = 9;
io.token.proto.common.notification.TransferFailed transfer_failed = 10;
io.token.proto.common.notification.TokenCancelled token_cancelled = 11;
io.token.proto.common.notification.BalanceStepUp balance_step_up = 12;
io.token.proto.common.notification.TransactionStepUp transaction_step_up = 13;
io.token.proto.common.notification.EndorseAndAddKey endorse_and_add_key = 14 [deprecated = true];
io.token.proto.common.notification.RecoveryCompleted recovery_completed = 15 [deprecated = true];
io.token.proto.common.notification.NotificationInvalidated notification_invalidated = 16;
io.token.proto.common.notification.CreateAndEndorseToken create_and_endorse_token = 17;
}
}
// The status returned when sending a notification. ACCEPTED means the notification was initiated, but
// not necessarily successfully delivered
enum NotifyStatus {
INVALID = 0;
ACCEPTED = 1;
NO_SUBSCRIBERS = 2;
}
// The record of a notification. Retrieved from notification service
message Notification {
// A unique ID given to this notification
string id = 1;
// The subscriber that was or will be notified
string subscriber_id = 2;
// Contents of the notification
NotificationContent content = 3;
enum Status {
INVALID = 0;
PENDING = 1; // Actions of the member are pending
DELIVERED = 2 [deprecated = true];
COMPLETED = 3; // The member has completed the notification
INVALIDATED = 4; // The notification has been invalidated
NO_ACTION_REQUIRED = 5; // No action required for the member
DECLINED = 6; // The member has declined the notification
}
Status status = 4;
}
// The contents of a notification that was sent or will be sent
message NotificationContent {
string type = 1; // Notification type
string title = 2; // Optional notification message title
string body = 3; // Optional notification message body
string payload = 4; // Notification payload
int64 created_at_ms = 5; // Time of creation
string loc_key = 6; // Notification message localization key
repeated string loc_args = 7; // Notification message localization arguments
}
message NotifyRequest {
io.token.proto.common.notification.Notification notification = 1;
io.token.proto.common.subscriber.Subscriber subscriber = 2;
}
message NotifyResponse {}
In NotificationService …
rpc Notify (NotifyRequest) returns (NotifyResponse) {
option (google.api.http) = {
post: "/notifications"
body: "*"
};
}
POST "/nofifications"
body = {
"notification": {
"id": "ZVPhQLaYnQMcWYcNjMYT",
"subscriberId": "5MDo6YbGu6M0qRRBZECl",
"content": {
"type": "LINK_ACCOUNTS",
"title": "Add accounts to Token",
"body": "Confirm adding your Iron Bank accounts to Token",
"payload": "Zm91ciBmaXZlIHNpeAo=",
"createdAtMs": "1499301354256",
"locKey": "LINK_ACCOUNTS",
"locArgs": ["Iron Bank"]
},
"status": "PENDING"
}
}
response = {}
Embedded SDK
Here you can find documentation for Token’s Embedded SDK.
Fallback Solution
Banks are required to provide an alternate path to the clients of TPPs, allowing them to access banking features if TokenOS (the open API channel) is unavailable.
Banks must:
- Provide an authentication endpoint (redirect URL) with the TPP’s credentials as headers
- When the user lands on the redirect URL, the banking authentication endpoint validates the TPP credentials and allows the user through their online banking flow
- Generate audit logs for every action of the user during the flow that include the TPP context, indicating the session was done as part of the fallback requirement
- Generate reports on validation calls of TPPs
- Generate reports on TPP access of the fallback option vs. TPP access of the PSD2-compliant API
The steps above assume that banks already:
- Provide an authentication endpoint (redirect URL) for bank customers to authenticate their identity
- Provide online banking functionality
- Have secure channels (mobile, online) and are compliant with PSD2
- Generate audit logs for their mobile and online banking applications for every action that the user takes in each session
- Know which TPPs are registered with the bank
If the bank’s online system is down, there is no fallback, and it is treated as a Priority 1 (critical) incident, with all necessary employees working to solve the problem. The Financial Conduct Authority (FCA) must be periodically updated on the progress of the restoration of service.
This solution does not guarantee coverage for all use cases for all TPPs.
Copyright © 2019 Token, Inc. All Rights Reserved