NAV Navbar
Javascript Java C#

Overview of Token

Token provides a global open banking API to access account data or initiate payments. The Token cloud is integrated with banks and other financial institutions through a thin integration layer that allows access to a bank’s core banking services. You can connect to the Token sandbox using SDK’s in your preferred programming language. Token uses PKI (public key infrastructure) to secure communications between the Token cloud and its clients.

The following diagram shows Token’s ecosystem:

Token Ecosystem

The On-boarding Process

The on-boarding process is simple. The sandbox allows you to play around using fake money and banks, so you can be sure your code works. You do not need to register or perform compliance checks before you are ready.

eIDAS Verification: If you been directed to Token by the bank you wish to access, please go here to learn how to create a member. These TPPs are required to identify themselves to banks using their QSEAL (Qualified Certificate for Electronic Seals) eIDAS (electronic IDentification Authentication Services) to receive access to their bank’s sandbox and APIs.

When your code is ready for production, you can contact Token to discuss the next steps for switching to our production environment OR follow the eIDAS-specific instructions.

Accessing the Sandbox

  1. Read Getting Started, download the SDK in your preferred language, and download sample codes.

  2. Create a Business Member. Verification of your alias is unnecessary in the sandbox, allowing you to interact with the SDK right away. You can create members, request payments, and access account information.

IMPORTANT: When reading the following documentation, please pay special attention to Getting Started, as it covers member creation and key generation. Instead of API keys, Token uses digital signatures (Public Key Infrastructure) so your information is safe and secure.

Getting Started

Setting Up the Client

Before you can do anything else, you must set up the Token client object (TokenClient). The client interacts with the Token network, with its configuration controlling the connection. It sends and receives calls and specifies which Token environment to use when testing. The client also functions as an abstraction on top of the network connection reconnecting as needed and killing idle connections. Typically, one SDK client instance is used for all operations.

Before you make calls you will need the latest Token SDK in your preferred language.

While creating the client, you will specify the Sandbox environment as the environment to use.

The SDK shows how to set up an Token client that saves your user’s private keys in a directory named keys. If your code runs somewhere with a safer storage space, you can implement a KeyStore that stores keys elsewhere, using UnsecuredFileSystemKeyStore as a guide.

The SDK shows how to set up an Token client that saves your user’s private keys in a directory named keys. If your code runs somewhere with a safer storage space, you can write a crypto engine that stores keys elsewhere, using UnsecuredFileCryptoEngine as a guide.

The SDK shows how to set up an Token client that saves your user’s private keys in a directory named keys. If your code runs somewhere with a safer storage space, you can implement a IKeyStore that stores keys elsewhere, using UnsecuredFileSystemKeyStore as a guide.

Go here for information about Local Key Storage.

Creating the Client

Path keys = Files.createDirectories(Paths.get("./keys"));
TokenClient tokenClient = TokenClient.builder()
        .withKeyStore(new UnsecuredFileSystemKeyStore(keys.toFile()))
        .connectTo(SANDBOX)
        .build();
const Token = new TokenClient({
    env: 'sandbox',
    keyDir: './keys',
});
 Tokenio.Tpp.TokenClient tokenClient = Tokenio.Tpp.TokenClient.NewBuilder()
.ConnectTo(Tokenio.TokenCluster.SANDBOX)
.WithKeyStore(new UnsecuredFileSystemKeyStore(key.FullName))
.Build();

Relevant API:

Creating a Business Member

Creating a Business Member Using Your Domain

Before connecting to banks through TokenClient, you must first create a Token business member using the createMember method with the type DOMAIN.

NOTE: If you have already created a member, load your existing member. Otherwise, you will need to set up an alias along with your member.

Creating a Business Member

// the 2nd time it will fail because the name is taken.)
//
// In test environments, we can use this domain as an alias
// without verifying it; but in production, we can't.
const alias = {
    type: 'DOMAIN',
    value: `${Token.Util.generateNonce()}.com.noverify`,
};

// Create a member with keys stored in memory:
return await Token.createMember(
/**
 * Creates and returns a new token member.
 *
 * @return a new Member instance
 */
public static Member createMember() {
    // Create the client, which communicates with
    // the Token cloud.
{
    /// <summary>
    /// Creates and returns a new token member.
    /// </summary>
    /// <returns>a new Member instance</returns>
    public static TppMember CreateMember()

Relevant APIs:

Aliases and Verification

Aliases and Verification for Business Members Using your Domain

Members have human-readable and verifiable aliases. As a Token business member with the DOMAIN type, you must provide your business web domain as your alias.

An alias can only be claimed once. After that, it is no longer available to other members. An alias belongs to only one (1) member, and must be verified before it can be used.

Adding and Removing an Alias

Alias alias = Alias.newBuilder()
        .setType(DOMAIN)
        .setValue("verified-domain.com")
        .build();

// add the alias
member.addAliasBlocking(alias);

// remove the alias
member.removeAliasBlocking(alias);
const alias1 = (await member.aliases())[0]; // or member.firstAlias();
const alias2 = {
    type: 'EMAIL',
    value: 'alias2-' + Token.Util.generateNonce() + '+noverify@token.io',
};
await member.addAlias(alias2);

const alias3 = {
    type: 'EMAIL',
    value: 'alias3-' + Token.Util.generateNonce() + '+noverify@token.io',
};
const alias4 = {
    type: 'EMAIL',
    value: 'alias4-' + Token.Util.generateNonce() + '+noverify@token.io',
};
await member.addAliases([alias3, alias4]);

await member.removeAlias(alias1);
await member.removeAliases([alias2, alias3]);

Alias alias = new Alias
{
    Type = Alias.Types.Type.Domain,
    Value = "verified-domain.com"

};
// add the alias
member.AddAliasBlocking(alias);
// remove the alias
member.RemoveAliasBlocking(alias);

Verifying an Alias

DOMAIN type aliases need to be manually verified by Token. To register a domain alias:

  1. Add your domain name using the Member addAlias method.

  2. Send Token a message that includes your business member ID.

If you do not know your ID, you can retrieve it using the Member memberId method.

Relevant APIs:

Fetching an Alias

There are two ways to fetch aliases:

  1. Fetch a list of the member’s aliases by calling the Member aliases method.
  2. Fetch the first alias used by the member by calling the Member firstAlias convenience method.

Relevant APIs:

Resolving an Alias

To resolve a member’s identity, you can look up a member’s ID using their alias and/or profile.

If you want to find out if an alias already belongs to a member, call the resolveAlias method. This can be helpful in discovering if there is a typo.

Relevant APIs:

Resolving an Alias

const resolved = await Token.resolveAlias(alias4);
/* resolved alias has id and alias fields: */
{
"id": "m:4AaVmfKfY9DQ9tuqWJcEwq9VkXpo:5zKtXEAq",
"alias": {
  "type":"EMAIL",
  "value":"alias4-v6rpfo+noverify@token.io"}
  }
}
Alias alias = Alias.newBuilder()
        .setValue("user-email@example.com")
        .build();

// If this call fails then the alias does not correspond to an existing member.
TokenMember resolved = client.resolveAliasBlocking(alias);

// resolved member ID from alias
String memberId = resolved.getId();

// The resolved alias
// will have the correct type, e.g. EMAIL.
Alias resolvedAlias = resolved.getAlias();
{
    Alias alias = new Alias
    {
        Value = "user-email@example.com"

    };


    // If this call fails then the alias does not correspond to an existing member.
    TokenMember resolved = client.ResolveAliasBlocking(alias);

    // resolved member ID from alias
    string memberId = resolved.Id;

    // The resolved alias
    // will have the correct type, e.g. EMAIL.
    Alias resolvedAlias = resolved.Alias;

Load Existing Member

To obtain a Member object for an already-existing member, you must have a TokenClient, the member ID of an existing member, AND the member’s keys. Add the keys to your SDK’s keystore and then call TokenClient.getMember on the member ID.

This code uses the API:

Most of the newly created member data is stored in the Token cloud, such as public keys. Private keys remain in local storage.

Use already-stored private key

return tokenClient.getMemberBlocking(memberId);
member = Token.getMember(Token.UnsecuredFileCryptoEngine, mid);
return tokenClient.GetMemberBlocking(memberId);

Profile

Each business member has a profile that allows users to find the member they are searching for without the alias or ID. This display name must be your business name. This identifies you in push notifications sent to your users’ apps, avoiding confusion and unintentional purchase cancellations. E.g. “Southside is requesting €10” as opposed to “Unknown merchant is requesting €10”.

Getting the Profile

To get member profile information, call Member getProfile. This gets a Profile data structure containing the member’s display name.

Relevant APIs:

Setting the Profile Picture

Call Member setProfile method to set your display name. This method takes a Profile data structure.

Relevant APIs:

Example for Setting the Profile

const name = {
    displayNameFirst: 'Tycho',
    displayNameLast: 'Nestoris',
};
await member.setProfile(name);
const jpeg = loadPicture('tycho.jpg'); // file contents as byte array
await member.setProfilePicture('image/jpeg', jpeg);

const profile = await member.getProfile(member.memberId());
/* Profile structure: */
{
 displayNameFirst: 'Tycho',
 displayNameLast: 'Nestoris',
 originalPictureId: 'b:2aT7GiHkqhDzpLw...rfBaag4jw:5zKtXEAq'
}
Profile name = Profile.newBuilder()
        .setDisplayNameFirst("Tycho")
        .setDisplayNameLast("Nestoris")
        .build();
member.setProfileBlocking(name);
member.setProfilePictureBlocking("image/jpeg", PICTURE);

Profile profile = member.getProfileBlocking(member.memberId());
Profile name = new Profile
{
    DisplayNameFirst = "Tycho",
    DisplayNameLast = "Nestoris"
};

member.SetProfileBlocking(name);
member.SetProfilePictureBlocking("image/jpeg", PICTURE);

Profile profile = member.GetProfileBlocking(member.MemberId());

Keys

Each member has cryptographic keys used to endorse or sign a token.

When you create a member with the createMember method, it automatically creates three (3) key pairs:

  1. a low-level pair
  2. a standard pair
  3. a privileged pair

Each pair has a private and a public key. The three public keys are uploaded to the Token cloud; the private keys are put into secure local storage.

The private keys will be used to sign all requests made through the Member object, such as initiating requests for account access or payments, or changing the Member’s profile. Token will verify each request using the public keys. Requests made directly from the TokenClient, such as resolving an alias or retrieving a list of connected banks, are not signed and are therefore accessible by anyone.

The following methods control keys in the Token Cloud ONLY.

To add more keys: call the Member approveKey or Member approveKeys method.

To remove previously-uploaded keys, call Member removeKey or Member removeKeys.

IMPORTANT: Removed keys are no longer valid and cannot be used to sign requests.

Relevant APIs:

Generating, Approving, and Removing Keys

Key lowKey = crypto.generateKey(LOW);
member.approveKeyBlocking(lowKey);

Key standardKey = crypto.generateKey(STANDARD);
Key privilegedKey = crypto.generateKey(PRIVILEGED);
member.approveKeysBlocking(Arrays.asList(standardKey, privilegedKey));

member.removeKeyBlocking(lowKey.getId());
const keypair4 = await Token.Crypto.generateKeys('LOW');
keypair4.publicKey = Util.strKey(keypair4.publicKey);
await member.approveKey(keypair4);
const keypair5 = await Token.Crypto.generateKeys('STANDARD');
const keypair6 = await Token.Crypto.generateKeys('PRIVILEGED');
keypair5.publicKey = Util.strKey(keypair5.publicKey);
keypair6.publicKey = Util.strKey(keypair6.publicKey);
await member.approveKeys([keypair5, keypair6]);

await member.removeKey(keypair4.id);
await member.removeKeys([keypair5.id, keypair6.id]);
Key lowKey = crypto.GenerateKey(Key.Types.Level.Low);
member.ApproveKeyBlocking(lowKey);

Key standardKey = crypto.GenerateKey(Key.Types.Level.Standard);
Key privilegedKey = crypto.GenerateKey(Key.Types.Level.Privileged);

Local Key Storage

To configure where the client stores member’s private keys, choose a CryptoEngine type and pass it as a parameter to Token.createMember, Token.provisionDevice, and Token.provisionDeviceLow.

The Token SDK client class provides some built-in key storage CryptoEngine types:

  • Token.MemoryCryptoEngine keeps keys in memory and forgets them on restart, which is useful for unit tests, but not for persistent members.
  • Token.UnsecuredFileCryptoEngine keeps keys in files in a directory. You must specify which directory as a parameter when creating the Token SDK client.
  • Token.BrowserCryptoEngine keeps keys in browser’s localStorage, which is useful for web clients.

You can define another CryptoEngine key storage type if you don’t want to use the one above. One option for implementation is the BrowserCryptoEngine implementation - a thin wrapper around BrowserKeyStore and stores keys. Use the KeyStoreCryptoEngine helper class to ease defining a similar, thin, wrapper.

For more information about implementing a custom CryptoEngine, see the SDK’s README.

Relevant APIs:

To configure where the client stores member’s private keys, choose the KeyStore implementation and pass it as a parameter to TokenClient.builder().withKeyStore().

The Token SDK client class provides basic built-in KeyStore classes:

  • InMemoryKeyStore keeps keys in memory, but forgets on restart. This is useful for unit tests, but not for persistent members.
  • UnsecuredFileSystemKeyStore keeps keys in files in a directory. You must specify which directory as a constructor parameter.

You can define another KeyStore class if you don’t want to use the one above.

Relevant APIs:

To configure where the client stores member’s private keys, choose the IKeyStore implementation and pass it as a parameter to TokenClient.Builder.WithKeyStore().

The Token client class provides basic built-in IKeyStore classes:

  • InMemoryKeyStore keeps keys in memory, and forgets on restart. This is useful for unit tests, but not for persistent members.
  • UnsecuredFileSystemKeyStore keeps keys in files in a directory. You must specify which directory as a constructor parameter.

You can define another IKeyStore class if you don’t want to use the one above.

Going into Production

Token Customer/Manual Registration

  1. When you are ready for production, connect to the production environment by updating connectTo(SANDBOX) to connectTo(PRODUCTION) or prd if you are using JavaScript. See Setting up the Client.

  2. For verification, Token will need your Alias value and type, and your memberId See Creating a Business Member.

  3. Contact Token Support using the TPP On-boarding category. A support ticket will be generated for you.

  4. Token will perform KYC (Know Your Customer) compliance checks, including checking your eIDAS credentials. Token will also verify your domain alias at this time.

  5. When this process is complete, you will be informed through your support ticket.

The Token Request Flow

For businesses, the most fundamental part of Token is creating a TokenRequest. This is how you will request payments and data.

What are Smart Tokens?

As mentioned in the Overview, smart tokens are at the core of TokenOS.

The diagram below shows the components of a smart token:

Image showing smart token components

There are two kinds of smart tokens:

Tokens are created by the Token app. To begin this process you must create a TokenRequest which will be sent to the app. The created token will mostly consist of details specified on the TokenRequest.

1. Creating a Token Request

A TokenRequest consists of the following fields:

to Recipient of the token, specifiable by member ID or alias
description Description of the payment or access
redirectUrl Defines the callback URL where your server will initiate redemption of the token
refId Reference ID for the token
callbackState Developer-specified string that allows state to be persisted between the the request and callback phases of the flow.
actingAs Optional: Entity that the to member is acting as a proxy for
destinationCountry Optional: Used to narrow down the country selection in the web-app UI
userRefId Optional: Used to track a member claimed by a TPP
customizationId Optional: Used for customizing the UI of the web-app
bankId Optional: Specifies you would like to bypass the Token bank selection UI. See below for more details
from Optional: Specify if you would like to bypass the Token email input UI. See below for more details
sourceAccountId Optional: The account ID of the source bank account
receiptRequested Optional: True if a receipt should be sent to the from member’s default receipt location

A RequestId will be returned on submitting the TokenRequest. This ID references the stored TokenRequest and is used to verify that the conditions of the request are unchanged. It must be included in the URL when redirecting the user to Token to obtain authorization.

NOTE:The member creating the TokenRequest must match the member that redeems the token later in the flow.

Additionally, a TokenRequest expires within 60 minutes. If you do not receive a response within this time, you will have to create another request.

Requesting Transfer Tokens

To make a request for a transfer token, use the static TokenRequest.transferTokenRequestBuilder(amount, currency) method, where amount is the total lifetime amount of the token, and currency is the 3 letter (ISO4217) currency code (e.g. “EUR”).

You can set the following two additional fields in the transfer token request builder:

chargeAmount The maximum amount per change on a multi-use transfer token
transfer_destinations Destination account(s) for the payment. This will typically contain an IBAN for SEPA transfer, or Sort Code and Account Number for Faster Payments. Details about the TransferDestination object can be found here

Though optional, Token recommends setting a reference ID (not to be confused with the requestId) in the tokenBuilder, which is included in the request to transfer the funds. This allows businesses the ability to easily reconcile transactions against payments they receive.

IMPORTANT: Due to restrictions of particular payment rails, this value cannot exceed 18 characters in length.

Example of creating a Token Request for a Transfer Token

const tokenRequest = Token.createTransferTokenRequest(10.00, 'EUR')
    .setToMemberId(payee.memberId())
    .setFromAlias('EMAIL', 'payerEmail@gmail.com')
    .setDescription('Book Purchase')
    .setRedirectUrl('https://token.io/callback')
    .setBankId('iron');
TokenRequest request = TokenRequest.transferTokenRequestBuilder(100., "EUR")
        .setToMemberId(payee.memberId())
        .setDescription("Book purchase") // optional description
        .setRedirectUrl("https://token.io/callback") // callback URL
        .setFromAlias(Alias.newBuilder()
                .setValue("payer-alias@token.io") // user alias
                .setType(Alias.Type.EMAIL)
                .build())
        .setBankId("iron") // bank ID
        .setCsrfToken(generateNonce()) // nonce for CSRF check
        .build();

// Store token request
return payee.storeTokenRequestBlocking(request);
TokenRequest request = TokenRequest.TransferTokenRequestBuilder(100, "EUR")
        .SetToMemberId(payee.MemberId())
        .SetDescription("Book purchase") // optional description
        .SetRedirectUrl("https://token.io/callback") // callback URL
        .SetFromAlias(new Alias
        {
            Value = "payer-alias@token.io",
            Type = Alias.Types.Type.Email
        })
        .SetBankId("iron") // bank ID
        .SetCsrfToken(Util.Nonce()) // nonce for CSRF check
        .Build();

// Store token request
return payee.StoreTokenRequestBlocking(request);

Requesting Standing Order Tokens

The standing order token is used for recurring payments-that is, payments that happen at regular intervals. Standing orders can be executed in the following frequencies: daily (DAIL), weekly (WEEK), twice-weekly (TOWK), monthly, (MNTH), twice-monthly (TOMN), quarterly (QUTR), semiannually (SEMI), or yearly (YEAR).

You must specify the startDate and in most cases, the endDate of each standing order. For example, if you create a standing order from, 2019-01-01 to 2019-02-28 with the frequency TOMN, it is an order for 4 payments, 2 for the month of January and 2 for February. If you do not specify an end date, the payment will happen indefinitely.

To make a request for a standing order token, use the static TokenRequest.standingOrderBuilder() method and set the parameters. Those listed in the table below as required must be included in the request. The others are optional.

member Required. Payer of the token
amount Required. Amount per charge of the standing order token
currency Required. Currency for each charge in the standing order
frequency Required. ISO 20022 code for the frequency of the standing order: DAIL, WEEK, TOWK, MNTH, TOMN, QUTR, SEMI, YEAR
startDate Required. Start date of the standing order: ISO 8601 YYYY-MM-DD or YYYYMMDD.
endDate Required. End date of the standing order: ISO 8601 YYYY-MM-DD or YYYYMMDD. If not specified, the standing order will occur indefinitely.
destination Required. Adds a transfer destination to a transfer token request.
ProviderTransferMetadata Optional. Adds metadata for a specific provider.
destinationCountry Optional. Sets the destination country in order to narrow down the country selection in the web-app UI.

The TPP is responsible for initiating the standing order, but it is the responsibility of the bank to execute each payment. The bank will not initiate a standing order until the token has been redeemed.

const tokenRequest = Token.createStandingOrderTokenRequest(
    10, 'EUR', 'MNTH', '2020-02-15', '2021-02-15'
)
    .setDescription('Monthly music subscription')
    .setRedirectUrl('https://token.io/callback')
    .setFromAlias('EMAIL', 'grantorEmail@gmail.com')
    .setToMemberId(grantee.memberId())
    .setBankId('iron');

Customization

The Token Web App can be white-labelled through the use of our SDK; specifically, the logo and colour scheme. To receive permission to white-label, follow the instructions below:

  1. Set your business name. Call setProfile on your business member and set display_name_first to your business name.

  2. Send Token your member ID requesting permission to customize.

  3. Once permission has been granted, call createCustomization. This will return a customizationId.

  4. Include this customization ID in your TokenRequest.

Parameters

Setting your logo picture as a BlobPayload

Blob.Payload logo = Payload.newBuilder()
                    .setName("arbitrary name")
                    .setAccessMode(AccessMode.PUBLIC)
                      // type of image
                    .setType("image/png")
                      // your logo in ByteString format
                    .setData(bs)
                    .setOwnerId(merchantMember.memberId())
                      // your merchant member’s ID
                    .build();

A map from string to string where the keys denote CSS variables and the values denote the colours in hex format #AARRGGBB (AA being the alpha channel).

Here’s the list of color variables Token uses and their default values:

Variables Default Value
color-dark #FF1A262C
color-secondary #FF50546C
color-body-text #FF525F7F
color-buttons #FF757F99
color-outlines #FFB8BFCA
color-dividers #FFE9EAF0
color-background #FFFCFCFC
color-light #FFFFFFFF
color-primary #FF05A5F0
color-delete-error #FFEB5757
color-disabled-input #FFC4C4C4
color-success-green #FF16AB2B
color-transparent transparent

The values you specify in your map will override the default values for the given key.

NOTE: The most important one is colour-primary, as this is responsible for our overall blue theme. It is likely you will only need to customize this value.

The following parameters have been DEPRECATED:

IMPORTANT: If you are using your own NCA license, request to hide the Token consent screen completely and handle consent before the user is directed to Token. The merchant will need to contact Token directly to use this feature.

Relevant API:

Requesting Access Tokens

To make a request for an access token, use the static TokenRequest.accessTokenRequestBuilder(resources) method, where resources is a list of ResourceTypes.

ACCOUNTS Request access to basic information about a user’s account, such as accountType, and accountId
BALANCES Request access to a user’s account balances, or if singular, the balance of one account.
TRANSACTIONS Request access to the transaction history of a user’s account(s).
TRANSFER_DESTINATIONS Request access to the payment rails which can be used to transfer money to a user’s account. e.g. the BIC and account number for a SWIFT account.

getAccount() can be called on the user’s account with accountId to access other ResourceTypes.

Example of creating a Token Request for an Access Token

const tokenRequest = Token.createAccessTokenRequest(['ACCOUNTS', 'BALANCES'])
    .setDescription('Account and balance access')
    .setRedirectUrl('https://token.io/callback')
    .setFromAlias('EMAIL', 'grantorEmail@gmail.com')
    .setToMemberId(grantee.memberId())
    .setBankId('iron');
TokenRequest request = TokenRequest.accessTokenRequestBuilder(ACCOUNTS, BALANCES)
        .setToMemberId(grantee.memberId())
        .setRedirectUrl("https://token.io/callback") // callback URL
        .setFromAlias(Alias.newBuilder()
                .setValue("grantor-alias@token.io") // user alias
                .setType(Alias.Type.EMAIL)
                .build())
        .setBankId("iron") // bank ID
        .setCsrfToken(generateNonce()) // nonce for CSRF check
        .build();

return grantee.storeTokenRequestBlocking(request);
TokenRequest request = TokenRequest.AccessTokenRequestBuilder(ResourceType.Accounts, ResourceType.Balances)
        .SetToMemberId(grantee.MemberId())
        .SetRedirectUrl("https://token.io/callback") // callback URL
        .SetFromAlias(new Alias
        {
            Value = "grantor-alias@token.io",
            Type = Alias.Types.Type.Email
        })
        .SetBankId("iron") // bank ID
        .SetCsrfToken(Util.Nonce()) // nonce for CSRF check
        .Build();

return grantee.StoreTokenRequestBlocking(request);

Confirmation of Available Funds

CAF is used to check that an account has enough funds available when the user wants to make a payment.

CBPIIs (Card Based Payment Instrument Issuer)

If you are a CBPII, create an AccessTokenRequest using the createFundsConfirmationRequest() function. You must have the bankId and a BankAccount object to do this. Continue with the rest of the request flow. The bank should not prompt the user to select their account, but give consent for the account that has already been provided.

At the end of the flow, call confirmFunds() function, providing the accountId, amount, and currency. The response of this function is a boolean indication if the user has enough funds in the specified account. The account number will be found in the returned token token.payload.access.resources[0].funds_confirmation.account_id.

NOTE: This is NOT the same as the account number provided in the original Token Request.

PISPs

Payment Service Providers should create a standard TransferTokenRequest and use the setConfirmFunds() function to indicate that confirming available funds should be performed before the transfer.

IMPORTANT: Only certain banks support CAF to be used this way. If a particular bank does not support CAF, this field will be ignored.

Cross Border Payments

Due to international payments usually having fees and foreign exchange charges attached to them, Token provides TPPs an option to specify a callback URL, giving them the ability to make an informed decision about setting the TransferDestinations in the storeTokenRequest(tokenRequest) call. Token calls this provided URL when a user makes a selection of the bank and country of the account with which they want to make the payment.

Set the callback URL in the TokenRequest, but DO NOT set TransferDestinations yet.

Setting the Callback URL

TokenRequest tokenRequest = TokenRequest.transferTokenRequestBuilder(250, "EUR")
                .setToMemberId(payee.memberId())
                .setDescription("Book purchase")
                // This TPP provided url gets called by Token after the user selects bank and
                // country on the Token web app.
                .setSetTransferDestinationsUrl(setTransferDestinationsCallback)
                // This TPP provided Redirect URL gets called after Token is ready
                // for redemption.
                .setRedirectUrl("https://tpp-sample.com/callback")
                .setFromAlias(AliasProtos.Alias.newBuilder()
                        .setValue("payer-alias@token.io") // user alias
                        .setType(AliasProtos.Alias.Type.EMAIL)
                        .build())
                .setBankId("iron") // bank ID
                .setCsrfToken(generateNonce()) // nonce for CSRF check
                .build();

        String requestId = payee.storeTokenRequestBlocking(tokenRequest);

When Token sees that TransferDestinations has not been set it will call the callback URL provided by the TPP. Once that URL is called, the TPP will receive a few informational parameters that will allow them to make a decision on what accounts can be selected as TransferDestinations.

Parsing the Callback URL

TokenRequestTransferDestinationsCallbackParameters params =
                tokenClient.parseSetTransferDestinationsUrl(setTransferDestinationsCallback);

TPPs can check the following parameters:

parameters.getCountry() Country selected
parameter.getBankName() Bank selected
parameters.getSupportedTransferDestinationTypes() Payment types supported by the bank i.e. SEPA, FASTER_PAYMENTS, etc.

Once the TransferDestinations are chosen, they need to be set in the previously stored TokenRequest.

Set TransferDestinations

List<TransferDestination> transferDestinations = new ArrayList<>();
        if (params.getSupportedTransferDestinationTypes()
                .contains(DestinationCase.FASTER_PAYMENTS)) {
            TransferDestination destination = TransferDestination
                    .newBuilder()
                    .setFasterPayments(TransferDestination.FasterPayments
                            .newBuilder()
                            .setSortCode(generateNonce())
                            .setAccountNumber(generateNonce())
                            .build())
                    .build();
            transferDestinations.add(destination);
        } else {
            transferDestinations.add(TransferDestination
                    .newBuilder()
                    .setSepa(TransferDestination.Sepa
                            .newBuilder()
                            .setBic(generateNonce())
                            .setIban(generateNonce())
                            .build())
                    .build());
        }
        payee.setTokenRequestTransferDestinationsBlocking(requestId, transferDestinations);
    }

Sample Source

After this step, TPPs can call generateTokenRequestUrl and follow the rest of the Token flow.

String tokenRequestUrl =
           tokenClient.generateTokenRequestUrlBlocking(requestId);

NOTE: These are optional steps. TPPs can set the TransferDestinations in the original storeTokenRequest call if they do not want to use a callback. In that case, if there are multiple TransferDestinations set, the domestic payment system will be preferred over international payment system.

2. Redirect to TokenOS to Obtain Authorization

After creating a TokenRequest, construct a URL that directs to the Token web app.

tokenRequestUrl URL to TokenOS that guides your user through the necessary steps to create a token
requestId ID that references a previously created TokenRequest stored in TokenOS

Example of Constructing a Token Request Url

var tokenRequestUrl = Token.generateTokenRequestUrl(requestId);
return tokenClient.generateTokenRequestUrlBlocking(requestId);
var tokenRequestUrl = tokenClient.GenerateTokenRequestUrlBlocking(requestId);

After constructing the tokenRequestUrl, direct your front-end to visit that URL. There are two different ways you can do this:

Server-side Redirect

Send an HTTP POST to your back-end to initiate the token request creation in Step 1, and redirect using an HTTP 302.

Binding the Token Button

You can also create a Token Button that either redirects to, or creates a pop-up of, the Token web app in the user’s browser.

The redirect option is default for both the mobile and web browsers, though for web browsers, there is an option of using a pop-up to bring a better UX to your users.

To get both redirect and pop-up, use the token.createRequest function, which will automatically choose the best option depending on the user’s device.

Pop-up/Redirect Function

token.createRequest(
    function(redirect) { // non-async function for redirect
        // do some non-async actions here and execute
        // redirect callback with url immediately after
        redirect(url);
    },
    function(done, errorCallback) { // async function for popup
        // do any async actions here and
        // execute callback with url when done
        done(url);
    },
);

Creating a Redirect-Only Button

function createRedirectButton() {
    var token = new window.Token({
        env: 'sandbox',
    });

    // get button placeholder element
    var element = document.getElementById(elementId);

    // create the button
    var button = token.createTokenButton(element, {
        label: "Redirect Token Quick Checkout",
    });

    // create TokenController to handle messages
    var tokenController = token.createController();

    // bind the Token Button to the Token Controller when ready
    tokenController.bindButtonClick(
        button, // Token Button
        token.createRequest(
            function(redirect) {
                redirect("/transfer", {
                    amount: 4.99,
                    currency: "EUR",
                    description: "Book",
                });
            }
        ),
        function(done) { // redirect token request function
            // go to transfer
            done("/transfer"
                + "?amount=4.99&currency=EUR"
                + "&description=Book");
        },
        function(error) { // bindComplete callback
            if (error) throw error;
            // enable button after binding
            button.enable();
        },
    );
}

Creating a Button for both Pop-up and Redirect

function createPopupButton() {
    var token = new window.Token({
        env: 'sandbox',
    });

    // get button placeholder element
    var element = document.getElementById(elementId);

    // create the button
    var button = token.createTokenButton(element, {
        label: "Popup Token Quick Checkout",
    });

    // create TokenController to handle messages
    tokenController = token.createController({
        onSuccess: function(data) { // Success Callback
            // build success URL
            var successURL = '/redeem-popup'
                + '?data='
                + window.encodeURIComponent(
                    JSON.stringify(data));
            // navigate to success URL
            window.location.assign(successURL);
        },
        onError: function(error) { // Failure Callback
            throw error;
        },
    });

    // bind the Token Button to the Token Controller when ready
    tokenController.bindButtonClick(
        button, // Token Button
        token.createRequest(
            function(redirect) { // redirect
                redirect('/getTokenRequestUrl', {
                    amount: 4.99,
                    currency: "EUR",
                    description: "Book",
                });
            },
            function(done, errorCb) { // optional async request for popup
                fetch('/transfer', {
                    method: 'POST',
                    headers: {'Content-Type':
                        'application/json; charset=utf-8'},
                    body: '{"amount":4.99,"currency":'
                        + '"EUR","description":"Book"}',
                })
                .then(function(res) {
                    // extract body text from response
                    return res.text();
                })
                .then(function(tokenRequestUrl) {
                    // Execute callback
                    // with the Token Request URL
                    done(tokenRequestUrl);
                })
                .catch(function(e) {
                    errorCb('Error fetching Token Request URL');
                });
            },
        ),
        function(error) { // bindComplete callback
            if (error) throw error;
            // enable button after binding
            button.enable();
        },
        { // options
            desktop: 'POPUP',
        }
    );
}

For more information, please see the sample code below.

Example Token Request URL

https://web-app.token.io/request-token/rq:42w7yzgwJtN9fQVx78McJzEKiyU9:5zKtXEAq?lang=en

After the user is redirected to the request URL, they will be prompted to agree to the Token terms and conditions. After accepting, they will be taken to their bank where they will authenticate themselves and authorize the payment or data access transaction. Once the token has been created, TokenOS will redirect to the callback URL specified in the TokenRequest with an HTTP 302.

Example Token Request Callback URL

https://merchant-site.com/callback
?signature=signature&state=state&tokenid=tokenid

See SDK Methods for information about the above parameters.

NOTE: If you are integrating with a mobile app, you can initiate the token request creation in a WebView in Create a Token Request so the redirect is also in the form of an HTTP 302.

When your server-side code receives an HTTP GET request at the callback URL, it should use the Token SDK parseTokenRequestCallbackUrl method to extract the tokenId, and verify the signature and CSRF token.

In the case of the pop-up method, the request to the server could be an HTTP POST and contain JSON formatted data. It should use the Token SDK parseTokenRequestCallbackParams method to extract the tokenId, and verify the signature and CSRF token.

Example of Parsing a Token Request Callback URL

return await Token.parseTokenRequestCallbackUrl(callbackUrl, csrfToken);
TokenRequestCallback callback = tokenClient.parseTokenRequestCallbackUrlBlocking(
        callbackUrl,
        csrfToken);
TokenRequestCallback callback = tokenClient.ParseTokenRequestCallbackUrlBlocking(
    callbackUrl,
    csrfToken.Value);

Example of Parsing the Token Request Callback JSON Data

var result = await Token.parseTokenRequestCallbackParams(JSON.parse(data), req.session.nonce);
TokenRequestCallback callback = tokenClient.parseTokenRequestCallbackParamsBlocking(
        data,
        csrfToken);
TokenRequestCallback callback = tokenClient.ParseTokenRequestCallbackParamsBlocking(
    queryParams,
    csrfToken.Value);

3. Redeeming Tokens

Tokens must be redeemed to create a payment or access account information.

Redeeming Transfer Tokens

Use the server-side redeemToken method to create a Transfer. This initiates a payment (via one of Token’s supported rails: e.g. SEPA) from the user’s bank.

tokenId ID that refers to a Token stored in TokenOS
refID Optional. Client-supplied reference ID used for deduplicating requests. If not provided, Token will generate one
transferToken A Token retrieved from TokenOS using a tokenId
transfer Represents a payment. The status of the transaction can be retrieved using this object

Redeeming a Transfer Token

const transferToken = await payee.getToken(tokenId);

// Destination for sending the funds
const destination = {
    account: {
        sepa: {
            iban: '123',
        },
    },
};

// Payer redeems the token, getting a transfer
return await payee.redeemToken(
    transferToken,
    5,
    'EUR',
    'lunch',
    [destination],
    cartId);
Token transferToken = payee.getTokenBlocking(tokenId);

// Set token destination
TransferDestination tokenDestination = TransferDestination
        .newBuilder()
        .setToken(TransferDestination.Token.newBuilder()
                .setMemberId(payee.memberId())
                .setAccountId(accountId))
        .build();

// Payee redeems a transfer token.
// Money is transferred to a payee bank account.
Transfer transfer = payee.redeemTokenBlocking(
        transferToken,
        tokenDestination,
        // if refId not set, transfer will use token ref ID:
        cartId);


// Set token destination
TransferDestination tokenDestination = new TransferDestination
{

    Token = new TransferDestination.Types.Token
    {
        MemberId = payee.MemberId(),
        AccountId = accountId
    }

};



// Payee redeems a transfer token.
// Money is transferred to a payee bank account.
Transfer transfer = payee.RedeemTokenBlocking(
        transferToken,
        tokenDestination,
        // if refId not set, transfer will have random refID:
        cartId);

The transfer object (or its ID) returned can be used to retrieve the status of the transaction.

transferId ID that refers to a transfer stored in TokenOS
transfer A transfer retrieved from TokenOS using a transferId or created with redeemToken
status Represents a transaction status (processing, success, or failed)

Example of retrieving the status of a transfer

payee.getTransfer(transferId).then(function(transfer) {
        const status = transfer.status;
});
Transfer transfer = payee.getTransfer(transferId);
TransactionStatus status = transfer.getStatus()
Transfer transfer = payee.GetTransferBlocking(transferId).Result;
TransactionStatus status = transfer.Status;


Redeeming Standing Order Tokens

To redeem a Standing Order Token, call the redeemStandingOrder method with the tokenId. Upon redemption, you get a StandingOrderSubmission object. This object’s ID can be used to get the updated status of the standing order by calling getStandingOrderSubmission.

Redeeming a Standing Order Token

const standingOrderToken = await payee.getToken(tokenId);
// Payer redeems the token, getting a standing order submission
return await payee.redeemStandingOrderToken(standingOrderToken.id);

Redeeming Access Tokens

Use the server-side call forAccessToken to get a Representable object that represents the user. You can then call getBalance, getTransactions, on the Representable object to retrieve account information. These methods will only succeed if the specified access token has sufficient permissions.

grantee Member to whom the access token was granted
tokenId ID that refers to an access token stored in TokenOS
customerInitiated Optional. Flag to set if access is customer-initiated. This bypasses the TokenOS cache
balance Balance of the account

Redeeming an Access Token

Representable grantor = grantee.forAccessToken(tokenId, customerInitiated);
List<Account> accounts = grantor.getAccountsBlocking();

// Get the data we want
Money balance = accounts.get(0).getBalanceBlocking(STANDARD).getCurrent();
const representative = grantee.forAccessToken(tokenId);
const accounts = await representative.getAccounts();
if (!accounts) return;

// Get information we want:
const balance0 = await representative.getBalance(accounts[0].id(), config.KeyLevel.LOW);
/* List of accounts from getAccounts: */
[
    {
      "id": "a:B3EphxmCi9VfcJevZ8Ad9PLMf41onD6ENEp6aHBHFito:5zKcENpV",
      "name": "Checking-714790e2-220a-4017-830c-316cefeff371",
      "bankId": "iron"
    }
]
IRepresentable grantor = grantee.ForAccessToken(tokenId, customerInitiated);
var accounts = grantor.GetAccountsBlocking();

// Get the data we want
Money balance0 = accounts[0].GetBalanceBlocking(Key.Types.Level.Standard).Current;

The forAccessToken method allows you to specify which access token to use to access information. You should keep track of the association between your access tokens and your users.

Security Considerations

Flow Overview

Since the Token Request Flow is based on OAuth 2.0, it is strongly recommended that you implement protection against CSRF and “OAuth Cut and Paste” (slides). TokenOS helps you mitigate both threats by providing a way to cryptographically bind CSRF tokens to the token ID.

This modifies the above outlined flow by introducing a step before the beginning of the process.

Token Request Flow Secure Sequence Diagram

0. Authenticate the Browser - Front-end authenticates with back-end service and creates a session.
1. Create a Token Request - Back-end service generates csrfToken as hash of session cookie, creates a tokenRequest, and constructs tokenRequestUrl
- Back-end service responds with HTTP 302 requesting redirect to tokenRequestUrl.
2. Redirect to TokenOS to obtain authorization - Front-end visits tokenRequestUrl.
- User proceeds through TokenOS steps.
- TokenOS redirects to the callback URL hosted by your back-end service
3. Redeem the Token - Back-end service parses the callback URL, validates csrfToken, and obtains the tokenId and optional state.
- Back-end service uses tokenId to redeem to access account information or create a payment.

The Smart Token Request Flow is a web-based flow. In order to mitigate the above attacks, the first step is to authenticate your user in a web-based environment. In a typical application the user is already logged in, so this may mean something different depending on if you are working with a website or a mobile application.

A web application does not need to implement anything additional if the user is already authenticated.

A mobile application will need to authenticate the user in the WebView. In order to avoid authenticating user on a separate web page you may want to share sessions between the mobile application and the WebView, or use the existing mobile app session to create a new session in the WebView.

SDK Methods

There are two methods in the Token SDK to help with improving your application security.

String TokenClient.generateTokenRequestUrl(requestId)
Generates a token request URL that can be used to initiate the token request process.

TokenRequestCallback TokenClient.parseTokenRequestCallbackUrl(callbackUrl, csrfToken)
Parses a Token Request callback URL, validates the cryptographic signature binding the csrfToken, state, and tokenId, and returns the tokenId and state.

csrfToken Unique string bound to the session of the user (e.g. the hash of a session cookie or associated with server-side user session). The csrfToken set in the TokenRequest builder and passed in parseTokenRequestCallbackUrl to validate that the same user session initiated and completed the request.

TokenOS suggests that you follow the IETF-recommended mitigation method of binding the CSRF token to the user’s authenticated state (using, for example, a the hash of a session cookie).

Note: Sensitive strings (such as sessionCookie) can be used directly as the csrfToken in the Token SDK since it hashes the token before using it. For more details on CSRF attacks against OAuth 2.0 and mitigation techniques, refer to this rfc.

If your application does not have user authentication, you can use Util.generateNonce to generate a secure random string to use as a CSRF token.
state Developer-specified string that allows state to be persisted between the the request and callback phases of the flow.

IMPORTANT NOTE: The state parameter can contain additional application-specific information, but should not be used to authenticate a user. The authentication must be performed prior to the initiation of the Token Request Flow, and the callback should use the same authenticated session.


Construct the token request URL using either the generateTokenRequestUrl or generateTokenRequestParams method from Step 2.

requestId ID of the TokenRequest to be used for this request.

Example of using generateTokenRequestUrl with a CSRF token

const tokenRequestUrl = Token.generateTokenRequestUrl(requestId);
return tokenClient.generateTokenRequestUrlBlocking(requestId);
var tokenRequestUrl = tokenClient.GenerateTokenRequestUrlBlocking(requestId);


Retrieve the state when parsing the callback url in Step 3 by passing in the same csrfToken that was set in the TokenRequest in Step 1.

callbackUrl The callback URL of your backend-service, along with query parameters
tokenId ID of requested token. Can be redeemed for information or payment initiation.

Example of using parseTokenRequestCallbackUrl with a CSRF token

parseTokenRequestCallbackUrl(callbackUrl, csrfToken).then(function(res) {
        const tokenId = res.tokenId;
        const state = res.innerState;
})
TokenRequestCallback callback = tokenClient.parseTokenRequestCallbackParamsBlocking(
TokenRequestCallback callback = tokenClient.ParseTokenRequestCallbackUrlBlocking(

Download Sample Code

If you’d like to see a working implementation of the Token Request Flow, check out our samples below:

Clone or download the appropriate sample. Each sample contains a reference back-end service implementation and a reference front-end website. Instructions are in the readme file.

Extensions

Token provides optional extensions to further customize the user experience.

Optional: Displaying Your Own Bank Selection UI

The first screen your user will see when they are redirected to TokenOS is a bank selection screen which prompts them to choose which bank to use.

By specifying in your application the bankId of the user-selected bank when creating the token request, you can filter the banks available to the user or control the bank selection UI.

Bank Selection

To retrieve the list of banks to display, use the getBanks method.

Example of using getBanks

Token.getBanks().then(function(banks) {
        String bankId = banks[0].id;
})
List<Bank> banks = tokenClient.getBanksBlocking();
String bankId = banks.get(0).getId(); // take the first bank id
PagedBanks banks = tokenClient.GetBanks().Result;
string bankId = banks.Banks[0].Id;

Each bank also specifies additional properties which can be used to filter the banks list displayed to the user:

supports_information Allows for retrieval of account information
supports_send_payment Allows for payment initiation
supports_receive_payment Allows for receiving payments
provider Underlying connectivity type; *e.g. Yodlee FinAPI Token
country ISO 3166-1 alpha-2 two letter country code in uppercase

After obtaining the user-selected bankId, provide it in the TokenRequest.

Example of specifying a Bank ID in the Token Request

const tokenRequest = Token.createTransferTokenRequest(10, 'EUR')
    .setBankId(bankId);
tokenRequest.setBankId(bankId)
var options = new TokenRequestOptions
{
    BankId = "iron"
};

When TokenOS displays the TokenRequest, it will see that a bankId has been specified and default to that bank instead of displaying the bank selection screen.

Optional: Provide User Alias For a Better UX

In the case of a Token integrated bank, your user may be prompted to provide their alias in order to enable a customer experience that does not involve entering bank credentials.

To provide an even more seamless experience, you have the option of specifying the user’s email address when creating the TokenRequest.

Example of Specifying the User Email Address in the Token Request

const tokenRequest = Token.createTransferTokenRequest(10, 'EUR')
                  .setFromAlias({type: 'EMAIL', value: email});
// Create a TokenRequest to be stored
TokenRequest request = TokenRequest.transferTokenRequestBuilder(10, "EUR")
                .setFromAlias(Alias.newBuilder()
                        .setType(Alias.Type.EMAIL)
                        .setValue(email)
                        .build());
var tokenRequest = new TokenRequest
{
    RequestOptions = new Proto.Common.TokenProtos.TokenRequestOptions
    {
        From = new TokenMember
        {
            Alias = new Alias
            {
                Type = Alias.Types.Type.Email,
                Value = email
            }
        }
    }
};

By providing the email, TokenOS will skip the email input screen entirely and ask the user to directly approve the request on their app.

Approve Screen

Optional: View Token Terms

Upon receiving a token, you can check the terms of the token and inspect the Token object returned by retrieving it with the getToken method.

Example of getting the token

payee.getToken(tokenId).then(function (token) {
        // Verify terms of the token
});
Token transferToken = payee.getTokenBlocking(tokenId);
Token transferToken = payee.GetToken(tokenId).Result;

Optional: Creating a Token on Behalf of Another Party

If you have been given permission by Token to do so, you can create a token on behalf of another party. To do that you need to set the actingAs field on the token payload with the following properties describing the intended recipient:

display_name Name of recipient, to be shown to user
ref_id Optional. Your reference ID of the recipient. Opaque to Token
logo_url URL pointing to recipient’s logo
secondary_name Optional. Domain or email of the recipient, to be shown to user along with display_name

Example of setting the actingAs property

// Transfer Token Request
const payload = {
    to: {
        id: payee.memberId(),
    },
    actingAs: {
        displayName: displayName,
        refId: refId,
        logoUrl: logoUrl,
        secondaryName: secondaryName,
    },
    transferBody: {
        lifetimeAmount: '100.00',
        currency: 'EUR',
    },
    description: 'Book purchase',
    redirectUrl: 'https://tpp-website.com/callback',
};

// Access Token Builder
const payload = {
    to: {
        id: grantee.memberId(),
    },
    actingAs: {
        displayName: displayName,
        refId: refId,
        logoUrl: logoUrl,
        secondaryName: secondaryName,
    },
    accessBody: {
        type: ['ACCOUNTS', 'BALANCES'],
    },
    redirectUrl: 'https://tpp-website.com/callback',
};

tokenRequest.setActingAs(ActingAs.newBuilder()
    .setDisplayName(displayName)
    .setRefId(refId)
    .setLogoUrl(logoUrl)
    .setSecondaryName(secondaryName));
// Transfer Token Builder
var payload = new TokenRequestPayload
{
    RefId = tppRefId,
    RedirectUrl = "https://tpp-website.com/callback",
    To = new TokenMember
    {
        Id = member.MemberId()
    },
    ActingAs = new ActingAs
    {
        DisplayName = displayName,
        LogoUrl = logoUrl,
        RefId = refId,
        SecondaryName = secondaryName
    },
    Description = "Book purchase",
    CallbackState = tppState,
    TransferBody = new TokenRequestPayload.Types.TransferBody
    {
        Amount = "10.0",
        Currency = "EUR"
    }
};

// Access Token Builder
var payload = new TokenRequestPayload
{
    RefId = tppRefId,
    RedirectUrl = "https://tpp-website.com/callback",
    To = new TokenMember
    {
        Id = member.MemberId()
    },
    ActingAs = new ActingAs
    {
        DisplayName = displayName,
        LogoUrl = logoUrl,
        RefId = refId,
        SecondaryName = secondaryName
    },
    CallbackState = tppState,
    AccessBody = new TokenRequestPayload.Types.AccessBody
    {
        Type = {types}
    }
};

Retrieving and Canceling Tokens

Get Token

You can retrieve an existing token by calling getToken on the token ID. This way, you can check on the status of the token by viewing the token’s signatures. For example, if the payer or grantor has canceled the token, then a CANCELLED signature will be present on the retrieved Token object.

Example of Retrieving a Token

Token token = member.getTokenBlocking(tokenId);
const transferToken = await payee.getToken(tokenId);
Token token = member.GetTokenBlocking(tokenId);
/* result with canceled token. It has both ENDORSED
and CANCELED payloadSignatures */
{
  "token": {
    "id": "tt:7ECKSivyfXzQzp8fAw9Z4aRkedPCKfPPuNH1MiprEmig:5zKcENpV",
    "payload": {
      "version": "1.0",
      "refId": "jh3y2pckfore57u4qp747k3xrqmix3i7kp9irtdtjmdixgk3xr",
      "issuer": {
        "username": "iron"
      },
      "from": {
        "id": "m:2YLns17mwhzbNYMofToaP34kFAYx:5zKtXEAq"
      },
      "expiresAtMs": "1502303565723",
      "transfer": {
        "redeemer": {
          "id": "m:2X16B4ZTwqejz9YC3so9JDkbXL6d:5zKtXEAq",
          "username": "iqzsaszle5shuscndt8wfusorrxj8xrvi5fs0l2q5fdb6e0zfr"
        },
        "instructions": {
          "source": {
            "account": {
              "token": {
                "memberId": "m:2YLns17mwhzbNYMofToaP34kFAYx:5zKtXEAq",
                "accountId": "a:GBTQ7DEbbKyo4wKarkkS3NaAMJiMbPnzf595dAELEaWW:5zKcENpV"
              }
            }
          }
        },
        "currency": "EUR",
        "lifetimeAmount": "100",
        "pricing": {
          "sourceQuote": {
            "id": "17aa3c61992f4061bdf04b7bb48eef33",
            "accountCurrency": "EUR",
            "feesTotal": "0.25",
            "fees": [
            {
              "amount": "0.17",
              "description": "Transaction Fee"
            },
            {
              "amount": "0.08",
              "description": "Initiation Fee"
            }
            ],
            "expiresAtMs": "1502303565723"
          },
          "instructions": {
            "feesPaidBy": "SHARED_FEE",
            "fxPerformedBy": "SHARED_FX"
          }
        }
      }
    },
    "payloadSignatures": [
    {
      "action": "ENDORSED",
      "signature": {
        "memberId": "m:2YLns17mwhzbNYMofToaP34kFAYx:5zKtXEAq",
        "keyId": "EVknJb1X_wqpyVc8",
        "signature": "1fO5tXvfB27P4_lYgerjxTyYX1q-n-TUdTmn0w-awEBuG0ml-6jEhkQFQxnDFTzGHlYVodIaggtxegsSV60lCg"
      }
    },
    {
      "action": "ENDORSED",
      "signature": {
        "memberId": "m:36XFB8Cx257rJb265Fa5FAkrXN7M:5zKtXEAq",
        "keyId": "rki_qdKsD9al2BQf",
        "signature": "EE3Zk5VF257RFKcWs0JxyrXnkOCXB3EagATzfOSA_q7eltqTq_mUIL-zP8jT2r-vmRPHd_3BBEVTQtxgFuhjDw"
      }
    },
    {
      "action": "CANCELLED",
      "signature": {
        "memberId": "m:2YLns17mwhzbNYMofToaP34kFAYx:5zKtXEAq",
        "keyId": "EVknJb1X_wqpyVc8",
        "signature": "drkAuAUhQx8m3enpmjyrrv7WSxDmZAPkWOE-AQbSIdyPjOcxKLXNTpKMZpoaRQjNsmAS7uBi9yr1VxnrrSXJDw"
      }
    },
    {
      "action": "CANCELLED",
      "signature": {
        "memberId": "m:36XFB8Cx257rJb265Fa5FAkrXN7M:5zKtXEAq",
        "keyId": "rki_qdKsD9al2BQf",
        "signature": "ZX6jpS6iax7DOydg1586Nb0SUu631ATolelWU28dcdAwqGlKW6P1fXHXmCF_sVf5Xqy0elLkda18T8Q88-QQDw"
      }
    }
    ]
  },
  "status": "SUCCESS"
}

Relevant APIs:

Canceling Transfer Tokens

You can cancel the transfer token any time. This will cancel any future payments the transfer token has authorized.

IMPORTANT: Previous payments and pending payments CANNOT be reversed or canceled.

Relevant APIs:

Example of Canceling a Transfer Token

// Retrieve a transfer token to cancel.
Token transferToken = payee.getTokenBlocking(tokenId);

// Cancel transfer token.
return payee.cancelTokenBlocking(transferToken);
const transferToken = await payee.getToken(tokenId);

// Payee cancels the token
return await payee.cancelToken(transferToken);
Token transferToken = payee.GetTokenBlocking(tokenId);

// Cancel transfer token.
return payee.CancelTokenBlocking(transferToken);

Canceling Access Tokens

As an access token’s grantee, you can cancel an access token at any time. This prevents retrieving future information with the access token.

Relevant APIs:

Example of Canceling an Access Token

// Retrieve an access token to cancel.
Token accessToken = grantee.getTokenBlocking(tokenId);

// Cancel access token.
return grantee.cancelTokenBlocking(accessToken);
// Grantee gets the token to see details
const accessToken = await grantee.getToken(tokenId);

// Grantee cancels the token
return await grantee.cancelToken(accessToken);
Token accessToken = grantee.GetTokenBlocking(tokenId);
// Cancel access token.
return grantee.CancelTokenBlocking(accessToken);

Get Transfer Tokens

The Member getTransferTokens method returns a paged list of transfer tokens for which the member was the payee. A paged list is a list of transfer tokens, starting with the most recently paid one and going back any number of tokens you wish to see. It also includes an offset that can be used to get any number of future transfer tokens.

This code uses the API:

Get Transfer Tokens

PagedList<Token, String> pagedList = member.getTransferTokensBlocking("", 10);
const pagedResult = await member.getTransferTokens('', 10);
PagedList<Token> pagedList = member.GetTransferTokensBlocking("", 10);

Get Access Tokens

The Member GetAccessTokens method returns a paged list of access tokens for which the member was the grantee. A paged list is a list of access tokens, starting with the most recently granted one and going back any number of tokens you wish to see. It also includes an offset that can be used to get any number of future access tokens.

Get Access Tokens

PagedList<Token, String> pagedList = member.getAccessTokensBlocking("", 10);
const pagedResult = await member.getAccessTokens('', 10);
PagedList<Token> pagedList = member.GetAccessTokensBlocking("", 10);

Get Standing Orders

AISPs can access a given standing order with the standingOrderId or a paged-list of standing order records.

Get Standing Orders

public static List<StandingOrder> redeemStandingOrdersAccessToken(
        Member grantee,
        String tokenId) {
    // Specifies whether the request originated from a customer
    boolean customerInitiated = true;

    // Access grantor's account list by applying
    // access token to the grantee client.
    // forAccessToken snippet begin
    Representable grantor = grantee.forAccessToken(tokenId, customerInitiated);
    List<Account> accounts = grantor.getAccountsBlocking();

    // Get the first 5 standing orders
    PagedList<StandingOrder, String> standingOrders = accounts.get(0)
            .getStandingOrdersBlocking(null, 5, STANDARD);

    // Pass this offset to the next getStandingOrders
    // call to fetch the next page of standing orders.
    String nextOffset = standingOrders.getOffset();

    return standingOrders.getList();
}

Errors

This section describes runtime errors thrown by the Token SDKs, such as program exceptions.

Java SDK Errors

The Token Java SDK throws the standard gRPC exceptions; for more information, refer to grpc-java Status.java. You will see the enum names in stack dumps.

Error Enum Value Meaning
OK 0 Operation completed successfully
CANCELLED 1 Operation was canceled (typically by the caller)
UNKNOWN 2 Unknown error; for example, a status value was received from an unknown error-space, or an API call returned an error with incomplete information
INVALID_ARGUMENTS 3 Client specified an invalid argument
DEADLINE_EXCEEDED 4 Deadline expired before operation could complete
NOT_FOUND 5 Requested entity (such as a file or directory) was not found
ALREADY_EXISTS 6 Entity that you attempted to create (such as a file or directory) already exists
PERMISSION_DENIED 7 Caller does not have permission to execute the specified operation
RESOURCE_EXHAUSTED 8 A resource, such as a per-user quota or the file system is out of space, has been exhausted
FAILED_PRECONDITION 9 Operation was rejected because the system is not in a state required for the operation’s execution
ABORTED 10 Operation was aborted, typically due to a concurrency issue
OUT_OF_RANGE 11 Operation was attempted past the valid range; for example, seeking or reading past the end of a file
UNIMPLEMENTED 12 Operation is not implemented or not supported/enabled
INTERNAL 13 Internal error
UNAVAILABLE 14 Service is unavailable, most likely due to a transient condition that might be corrected by retrying
DATA_LOSS 15 Unrecoverable data loss or corruption
UNAUTHENTICATED 16 Request does not have valid authentication credentials for the operation

JavaScript SDK Errors

The Token JavaScript SDK throws different types of HTTP errors. All errors are wrapped in an error object, with a message that contains the SDK method that failed, along with the reason for failure.

C# SDK Errors

The Token C# SDK throws System.AggregateException that wraps the standard gRPC exceptions; for more information, refer to grpc-csharp StatusCode.cs.

Error Enum Value Meaning
OK 0 Operation completed successfully
Cancelled 1 Operation was canceled (typically by the caller)
Unknown 2 Unknown error
InvalidArgument 3 Client specified an invalid argument
DeadlineExceeded 4 Deadline expired before operation could complete
NotFound 5 Requested entity (such as a file or directory) was not found
AlreadyExists 6 Entity that you attempted to create (such as a file or directory) already exists
PermissionDenied 7 Caller does not have permission to execute the specified operation
ResourceExhausted 8 A resource, such as a per-user quota or the file system is out of space, has been exhausted
FailedPrecondition 9 Operation was rejected because the system is not in a state required for the operation’s execution
Aborted 10 Operation was aborted, typically due to a concurrency issue
OutOfRange 11 Operation was attempted past the valid range; for example, seeking or reading past the end of a file
Unimplemented 12 Operation is not implemented or not supported/enabled
Internal 13 Internal error
Unavailable 14 Service is unavailable, most likely due to a transient condition that might be corrected by retrying
DataLoss 15 Unrecoverable data loss or corruption
Unauthenticated 16 Request does not have valid authentication credentials for the operation

Token SDKs

Java SDK

To use the Token Java SDK you will need Java Development Kit (JDK) version 8 or later.

To get the Token Java SDK JARs, source code, and instructions for use go to the token-sdk-tpp Token Artifactory page. (Choose the latest jar under token-sdk-tpp to use the latest version of the SDK.)

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-tpp</artifactId>
    <version>2.6.3</version>
</dependency>

Gradle

repositories {
  ...
  maven { url 'https://token.jfrog.io/token/public-libs-release-local/' }
}
compile(group: 'io.token.sdk', name: 'tokenio-sdk-tpp', version: '2.6.3')

JavaScript SDK

The JavaScript SDK builds code usable in Node.js or (via a build tool like webpack or browserify) in the browser. It uses ES7, but builds code usable in ES6.

Token requires a recent version of npm and yarn to build. To install the npm package: npm install @token-io/tpp

C# SDK

The SDK is based on C# 7.0. The target framework is .Net Framework 4.5.1. The package can be found on Nuget.

To use the SDK, add the package as a dependency to your project file:

<ItemGroup>
    <PackageReference Include="Token.SDK.TPP" Version="0.0.1" />
</ItemGroup>

Other Languages

To use a language other than the Token SDK languages (currently Java, JavaScript, and Objective-C, with additional languages to be added), you can use the Token gRPC or HTTP API. Many of the API endpoints require authentication; to authenticate a request, you must compute a cryptographic signature and attach it.

For information about how to do this, contact Token.

Protocol Buffers

Many important data structures are defined as Protocol Buffer messages.

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. For protocol buffers, you want the “regular” jars, not javadoc or sources. You can also see them as web pages.

The sample code below shows the protocol buffer for signature and how it is constructed in Java.

message Signature {
  string member_id = 1;
  string key_id = 2;
  string signature = 3;
}
Signature signature = Signature.newBuilder()
    .setMemberId(memberId)
    .setKeyId(signer.getKeyId())
    .setSignature(signer.sign(update))
    .build();

Many important data structures are defined in terms of Protocol Buffer messages.

For example, smart token signatures are defined in terms of the message Signature.

In Token Javascript, the object corresponding to a protocol buffer message has fields with camel-case names: protocol buffer foo_bar becomes Javascript fooBar.

A protocol buffer string becomes a Javascript string; a protocol buffer number becomes a Javascript number. A protocol buffer enum value becomes a Javascript string; for example, if an enum has a value ENDORSED = 1, in Javascript, this value is 'ENDORSED'. A repeated protocol buffer field becomes a Javascript array. A protocol buffer bytes becomes a Javascript string, the base64 encoding of those bytes.

You can also see the Token SDK protocol buffers as web pages.

The sample code below shows the protocol buffer for signature and how it is constructed in JavaScript.

message Signature {
  string member_id = 1;
  string key_id = 2;
  string signature = 3;
}
/* signature */
message Signature {
  memberId = "...",
  keyId = "...",
  signature = "..."
}

Many important data structures are defined as Protocol Buffer messages.

The build process generates C# code from these. You can learn more about this C# code. A quick start: If you have an object based on a protocol buffer definition, to get the value of a field named tool_name, get the property named ToolName. If that would give you a property whose name collides with the class that owns the property, e.g., Signature property inside a Signature class, get the property named Signature_ instead.

The C# SDK fetches the newest protocol buffer definitions when it is built. You can get the newest protocol buffer definitions by downloading the most recent jars. For protocol buffers, you want the “regular” jars, not javadoc or sources. You can also see them as web pages.

The sample code below shows the protocol buffer for signature and how it is constructed in C#.

message Signature {
  string member_id = 1;
  string key_id = 2;
  string signature = 3;
}
Signature signature = new Signature
{
    MemberId = Payer.Id,
    KeyId = keyId,
    Signature_ = signer.Sign(update)
};

Customer Experience

Depending on the underlying bank and its authentication and authorization method, the user experience may vary. The following screens are representative of most cases.

Example UX of a merchant requesting a bank direct payment using Token; the user is visiting the merchant website on a mobile device.

Screen 0 Screen 01 Screen 2 Screen 3 Screen 4 Screen 5 Screen 7 Screen 8

Security

TokenOS uses PKI (public key infrastructure) to secure communications between the Token cloud and its clients. Public keys are stored in the Token cloud and are publicly shared, requiring valid signatures to be changed. Private keys are never shared. Each Token API invocation is digitally signed by the caller using its private key(s), and the Token cloud and banks verify the API request by using the caller’s public key. The Token SDK greatly simplifies the development process by wrapping the complex PKI in a set of simple interfaces.

TokenOS supports the SCA (strong customer authentication) requirements of PSD2, whereby transfers over a threshold amount require two-factor authentication (2FA). 2FA requires that authentication include any two of the following:

Frequently Asked Questions

Do I need to have an AISP (account information service provider) or PISP (payment initiation service provider) license to use your service?

No, Token has obtained AISP and PISP approval by the FCA in the UK and passported across Europe. Please note than if you require AIS functionality you may be required to be registered as an agent of Token via the FCA. We will assist you with any required formalities please contact us for further information.

Do you have a sandbox that I can test with?

Yes, Token has a full sandbox with model banks for you to test your integration against. In the initial setup steps, point your SDK client to connect to our SANDBOX environment. We recommend you start out with Wood Bank (United Kingdom), contact us to learn more about the other banks we have available.

Can I test against sandboxes hosted by real banks in your network?

When you have completed your integration and have successfully tested against Token’s model banks, you will be able to test with one of the directly integrated partner banks (pursuant to PSD2 regulations). For more details don’t hesitate to contact us.

Can I set up a standing order or recurring payment using your Smart Token technology?

Yes.

Does Token offer categorization options for retrieving transaction history?

Token does not currently offer categorization; we expect to offer this service in the future.

Can I make cross-border payments?

Yes.

How do I notify Token of questions or issues I’m encountering?

Contact Us at our support desk, and we’ll get back to you as soon as we can.

Glossary

Term Definition
2FA Two-Factor Authentication
asset owner Party whose asset is contained in the smart token; for transfer tokens, the payer; for access tokens, the grantor
access token Smart token that provides authorization to access information or perform operations
AISP Account Information Service Provider; for example, Yodlee and Mint
API Application Programming Interface; for example, the underlying functions of the Token Java SDK
endorsement digital signature
grantee Token member who is authorized by another Token member (grantor) to retrieve information and perform operations on behalf of the grantor
grantor Token member who authorizes another Token member (grantee) to retrieve information and perform operations on his/her behalf
HSM Hardware Security Module
payee Merchant that receives money in exchange for goods
payer Consumer (end user or shopper) who makes a purchase and uses Token to pay the payee
PII Personally Identifiable Information
PISP Payment Initiation Service Provider; that is, online merchants
PKI Public Key Infrastructure; rules, datastores, and trusted entities that manage public-key encryption
PSD2 Payment Services Directive 2
RTS Regulatory Technical Standards
SCA Strong Customer Authentication; a requirement of RTS for PSD2
SEPA Single Euro Payments Area; payment-integration initiative of the EU
smart token Authorization to access an underlying asset
SDK Software Development Kit; for example, the Token Java SDK
Token cloud Layer through which TokenOS members communicate
transfer token Smart token that provides authorization to transfer assets, such as the funds in a bank account

Copyright © 2019 Token, Inc. All Rights Reserved