NAV Navbar
gRPC/Protobuf

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

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:

  1. Get the Token Bank Integration SDK. Get the sample bank server implementation.

  2. Generate keys and certificates for secure communication; keep track of them.

  3. Set up fake environment/data for testing. This probably means setting up an environment with some fake accounts.

  4. Get the sample bank server up and running. Normally this just works, but there are troubleshooting tips.

  5. 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.

  6. Replace fake parts of the sample bank server implementation with real implementations.

  7. 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:

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:

  1. The user launches the Token app on her phone.

  2. The Token app shows a list of banks. The user chooses her Bank.

  3. 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 and redirect_uri=uri as URL parameters.

    Sample Bank website login

  4. The user completes 2FA (two-factor authentication) triggered by the Bank, similar to their internet banking login process.

    2 Factor Auth

  5. 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.

    Sample Bank website login

  6. 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.

  7. The Bank’s web site redirects to the location in the earlier-specified redirect_uri parameter, appending #access_token=bearer token string

    When the web view’s location changes to this URL, the app gets the bearer token string and stops showing the web view.

  8. The app uses the Token SDK to send this bearer token string to TokenOS.

  9. Using the bearer token string, TokenOS requests linking information from the bank’s RPC server. The bank’s AccountLinkingService handles the request, returning a BankAuthorization structure containing the member ID and account information.

  10. 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:

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.)

  1. Prompt the user to sign in; authenticate the user. Once she authenticates…

  2. 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…

  3. 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.

  4. 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.

  1. The user is redirected to their bank’s authentication page (login).
  2. The user authenticates with the bank and chooses which account to use for payment.
  3. The bank displays a confirmation of payment.
  4. The user confirms the purchase with the bank.

Payment Initiation Flow

Diagram of Payment Initiation Flow

  1. The bank provides Token with a bank_url endpoint.
  2. 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&callback-url=callback_url

    Definitions
    auth-request-id A unique identifier used to retrieve the payload containing all information on the purchase. This payload needs to be signed by the bank, hence the name (Token is requesting authorization from the bank).
    callback-url A callback URL the user is redirected to at the end of the flow.
  3. When the bank receives the above information, it calls

  4. getAuthRequestPayload(authRequestId)
  5. using Token’s Bank SDK, where authRequestId is the unique identifier in the URL.

  6. This call returns a TokenPayload containing information about the purchase. If this call fails, 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.

  7. 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.

  8. 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.

  9. 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 calling payload.getRefId() and payload.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 as refId and initiatorId). The bank should use this pair to look up the user’s chosen account and perform the transfer.

  10. 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.

  11. Once the user confirms the purchase, the bank signs the payload using Member: SignTokenPayload from the SDK and produces the bank_signature. The result of signTokenPayload needs to be base64 encoded, and the result of THAT is the signature param in the URL mentioned in Step 10.

  12. The bank redirects the user using the callback_url in the following format:

    callback_url?auth-request-id=auth_request_id&signature=bank_signature

    Diagram of Payment Redemption Flow

    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.

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.

  1. The user is redirected to the bank’s authentication (login) page.
  2. The user authenticates with the bank and chooses which account they wish to grant the TPP access to (balance and transaction).
  3. The bank displays a confirmation page.
  4. The user confirms their consent for access.

Flow

Diagram of Access Information 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:

  1. The user opens the Token web app on the AISP’s website.

  2. Token shows a list of banks and the user chooses their bank.

  3. They click Proceed as Guest, then Token redirects them to a screen that prompts the user to log in to their bank.

    (This screen is a web page provided by the bank. When on-boarding, the bank told Token the URL of this page. Token passes in memberId=token_member_id and redirect_uri=uri as URL parameters).

  4. 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.

  5. 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”.

  6. The bank’s web site redirects to the location in the earlier-specified redirect_uri parameter, appending the hash fragment #access_token=bearer_token.

  7. The Token web app gets the bearer_token and sends it to TokenOS.

  8. Using the bearer_token, TokenOS requests linking information from the bank’s RPC server. The bank’s AccountLinkingService handles the request, returning a BankAuthorization structure containing the member ID and account information.

  9. 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

<repositories>
    ...
    <repository>
      <url>https://token.jfrog.io/token/public-libs-release-local/</url>
    </repository>
</repositories>
<dependency>
    <groupId>io.token.sdk</groupId>
    <artifactId>tokenio-sdk-integration</artifactId>
    <version>2.1.5</version>
</dependency>

Gradle

repositories {
  ...
  maven { url 'https://token.jfrog.io/token/public-libs-release-local/' }
}
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; // Account identifier, e.g., SWIFT transfer info
  CustomerData customer_data = 2;                        // Customer data: name and address
  string bank_id = 3;
}
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_GENERIC = 5;                  // the transaction has failed due to other reasons
  SENT = 16;                            // legacy transfers only: 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
}

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;
}

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;

  enum Status {
    INVALID = 0;
    ACTIVE = 1;
    INACTIVE = 2;
    PROCESSING = 3;
    FAILED = 4;
  }
}
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;
}

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 handles transfer requests. It has one required method-the transfer method-that takes a 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.

  1. Token makes a TransferRequest to the bank. Within this request is a transferId.

  2. The bank returns the transactionID and the status of the transaction.

IMPORTANT: the transferId and the transactionId are NOT the same.

If the status throws a TransferException with the PROCESSING status, Token will periodically call the GetTransaction endpoint with the transaction ID received in the the TransferRequest response to fetch the most recent transaction status.

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 AND/OR the bank periodically needs to throw a TransferException with PROCESSING as the status in a transfer response, 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 only required when the bank is not able to return the final transaction ID in the transfer response.

To find out which account is debited, call the Transfer object’s getAccount method. It will return the BankAccount structure that linked the account to Token.

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.

Relevant APIs:

Transfer Service

/**
 * Creates a transaction for the specified transfer request.
 *
 * @param transfer the token transfer request
 * @return bank transaction id for the requested transfer
 * @throws TransferException in case transfer fails
 */
String transfer(Transfer transfer) throws TransferException;

/**
 * Creates a bulk transfer for the specified request.
 *
 * @param request the Token bulk transfer request
 * @return bank-defined ID for the bulk transfer and status
 */
default BulkTransferResult createBulkTransfer(BulkTransferRequest request) {
    throw new StatusRuntimeException(UNIMPLEMENTED);
}

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

(This list last updated 2017-12-19)

Information Token Provides

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;
}
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}"
  };
}
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;
}
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}"
  };
}
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;
}
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}"
  };
}
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;
 }

message TransferResponse {
  string transaction_id = 1;
  StatusCode status = 2;
  string status_description = 3;
}
message TransferResponse {
  string transaction_id = 1;
  StatusCode status = 2;
  string status_description = 3;
}

In TransferService

rpc Transfer (TransferRequest) returns (TransferResponse) {
  option (google.api.http) = {
      post: "/transfers"
      body: "*"
  };
}
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;
    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.

Embedded Docs

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:

The steps above assume that banks already:

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