How to integrate with Web SDK

Follow the guide to set-up Web SDK integration

Required Flow

Before starting the integration with our Web SDK, it is important to understand the overall flow of requests coming from the SDK to Dapi's system. In addition to setting up the Web SDK, you will also need to configure a backend server. The purpose of the backend server is to provide an extra layer of security and allow you to confirm the information on your backend if required by your use case.

330

Flow with the Web SDK.

Setting-up App Backend Server (Node.js)

If you are not interested in setting up the server using Node.js, you can refer to the Server Side Libraries and follow any quickstart instructions for a different language.

Install npm libraries in your project

npm i @dapi-co/dapi-node
npm i express
npm i cors

Save the following file as server.js. You might need to install the other dependencies if they don't already exist on your machine.

var express = require('express')
const DapiApp = require('@dapi-co/dapi-node')
var cors = require('cors')

const app = express()
const port = 8060 // default port to listen
app.use(cors())
app.use(express.json())

const dapi = new DapiApp.default({
  appSecret: 'YOUR_APP_SECRET',
})

// define a route handler for the sdk
app.post('/dapi', async (req, res) => {

  try {
    
    const dapiResponse = await dapi.handleSDKDapiRequests(req.body, req.headers)
    res.send(dapiResponse)

  } catch (error) {

    //Handle Network Errors
    console.dir(error)
  }
})

// start the Express server
app.listen(port, () => {
  console.log(`server started at http://localhost:${port}`)
})

In the directory of server.js use the following command to run the server:

node server.js

Once the server is running successfully, also make sure to add the App Server URL to the Dapi Dashboard. In this way, the SDK will automatically know where to send its requests to.

If you followed the above NodeJS and Express example the App Server URL value is http://localhost:8060/dapi.

1836

After adding App Server URL on Dapi Dashboard

You are now all set to start your Web integration!

Web SDK Configuration

Include SDK source

To import Dapi SDK for Javascript simply add the following script in your root HTML file.

<script src="https://cdn.dapi.com/dapi/v2/sdk.js"></script>

Initialize and start the SDK

The following code snippet sets the configurations for the SDK.

var ba = null;

var handler = Dapi.create({
  environment: Dapi.environments.sandbox, //or .production
  appKey: "YOUR_APP_KEY", 
  countries: ["AE"],
  bundleID: "YOUR_BUNDLE_ID", // bundleID you set on Dashboard
  clientUserID: "CLIENT_USER_ID",  
  isCachedEnabled: true,
  isExperimental: false,
  clientHeaders: {},
  clientBody: {},
  onSuccessfulLogin: function(bankAccount) {
    ba = bankAccount; //explained in the next step
  },
  onFailedLogin: function(err) {
    if (err != null) {
      console.log("Error");
      console.log(err);
    } else {
      console.log("No error");
    }
  },
  onReady: function() {
    handler.open(); // opens the Connect widget
  },
  onExit: function() {
    console.log("User exited the flow")
  },
  onAuthModalOpen: function() {
    console.log("MFA modal opened")
	},
	onAuthModalSubmit: function() {
    console.log("MFA answer submitted")
	}
});
ParameterTypeDescription
environmentStringDapi environment - Sandbox or Production
appKeyStringYour app key from the dashboard
countriesString[]You can pass string of countries with country code eg AE, SA, US etc
bundleIDStringThe bundle id you injected on the dashboard
clientUserIDStringThis user's unique id on your app
isCachedEnabledBooleanSetting this to true makes the sdk inject the bank account object in browser cache.

Note: Set it to true only on trusted browsers
isExperimentalBooleanShow experimental banks if necessary
clientHeadersObjectIf you want to inject additional headers in the calls
clientBodyObjectIf you want to inject additional body params in the calls
onSuccessfulLoginFunctionCallback function that sends back a bankAccount object on successful login
onFailedLoginFunctionCallback function that sends back the error object if an error happened during user login process
onReadyFunctionCallback function called when the widget has successfully loaded
onExitFunctionCallback function called when the user closes the the SDK widget (exits the flow without completing it)
onAuthModalOpenFunctionCallback function called when the MFA authentication modal opens up
onAuthModalSubmitFunctionCallback function called when MFA authentication modal is closed by submitting an answer to the MFA prompt. Closing the modal without submitting the answer will not trigger this callback

Bank Account Object

As you might have noticed in the previous step, after a successful login you get a bankAccount object, which includes all the necessary information for any consequent calls.

  • Accessing the Data API endpoints: ba.data
  • Accessing the Payment API endpoints: ba.payment

Example for calling the Data API getIdentity endpoint:

onSuccessfulLogin: function(bankAccount) {
    ba = bankAccount; 
    ba.data.getIdentity()
      .then((identityResponse)=>{
      if (identityResponse.status === "done") {
          console.log(identityResponse.identity)
        } else {
          console.error("API Responded with an error")
          console.dir(identityResponse)
        }
    })
  },

Sample SDK

You can try out this sample page to try the SDK out. You can use the Web SDK for a step-by-step guide.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <meta name="author" content="" />
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <div
      style="display: flex; flex-direction: column; flex: 1; justify-content: center; align-items: center; width: 100%; height: 100%"
    >
      <h1>Client Website!!</h1>
      <button
        style="height: 2rem;width: 20rem;background: beige;border: 1px solid black; margin-top: 2rem;"
        onclick="clickMe()"
      >
        Quick Transfer
      </button>
    </div>
   <script src="https://cdn.dapi.co/dapi/v2/sdk.js"></script>
    <script>
      let connectLoading = true
      var ba = null
      
      var dapi = Dapi.create({
          environment: Dapi.environments.sandbox,
          appKey: "YOUR_APP_KEY",
          countries: ["AE"],
          bundleID: "YOUR_BUNDLE_ID",
          clientUserID: "CLIENT_USER_ID",
          isCachedEnabled: true,
          isExperimental: false,
          clientHeaders: {},
          clientBody: {},
          onSuccessfulLogin: function(bankAccount) {
            ba = bankAccount;
          },
          onFailedLogin: function(err) {
            if (err != null) {
              console.log("Error");
              console.log(err);
            } else {
              console.log("No error");
            }
          },
          onReady: function() {
            connectLoading = false
          },
        	onExit: function() {
   					console.log("User exited the flow")
  				}
        });
      var clickMe = function() {
          if (!connectLoading){
            dapi.open();
          } else {
              console.error("Widget is loading. Please wait!")
          }
      };
    </script>
  </body>
</html>

❗️

404 AppConfig Not Found

Make sure the bundleID value corresponds to the bundleID's value that you have set on Dapi Dashboard. Haven't set bundleID? See here.

Also make sure that you are using a valid appKey.

👍

Using React?

Quick set-up example using ReactJS can be found here

Accounts Modal

The Accounts Modal allows you to after a successful login prompt the user with a list of their accounts. The user will need to select one of the accounts in order to proceed. You can add a custom message to the user that will be displayed on the modal, such as "Select an account where you receive your salary"

Accounts Modal Complete Example

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title></title>
  <meta name="author" content="" />
  <meta name="description" content="" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
</head>

<body>
  <div
    style="display: flex; flex-direction: column; flex: 1; justify-content: center; align-items: center; width: 100%; height: 100%">
    <h1>Client Website!!</h1>
    <button style="height: 2rem;width: 20rem;background: beige;border: 1px solid black; margin-top: 2rem;"
      onclick="clickMe()">
      Quick Transfer
    </button>
  </div>
  <script src="https://cdn.dapi.co/dapi/v2/sdk.js"></script>
  <script>
    let connectLoading = true
    var ba = null

    var dapi = Dapi.create({
      environment: Dapi.environments.sandbox,
      appKey: "YOUR_APP_KEY",
      countries: ["AE"],
      bundleID: "YOUR_BUNDLE_ID",
      clientUserID: "CLIENT_USER_ID",
      isCachedEnabled: true,
      isExperimental: false,
      clientHeaders: {},
      clientBody: {},
      onSuccessfulLogin: function (bankAccount) {
        ba = bankAccount;

        ba.data.getAccounts()
          .then(payload => {

            ba.showAccountsModal(
              "Your message to the user",
              payload.accounts,
              (account) => {
                console.dir(account)
              },
              () => {
                console.dir("User Cancelled")
              })
          })
      },
      onFailedLogin: function (err) {
        if (err != null) {
          console.log("Error");
          console.log(err);
        } else {
          console.log("No error");
        }
      },
      onReady: function () {
        connectLoading = false
      },
      onExit: function() {
    		console.log("User exited the flow")
  		}
    });
    var clickMe = function () {
      if (!connectLoading) {
        dapi.open();
      } else {
        console.error("Widget is loading. Please wait!")
      }
    };
  </script>
</body>

</html>

Sending custom headers and body params

The Web SDK supports sending custom headers or body parameters. This feature will be useful if you want to send any information from the frontend to your backend.

You can send the headers and body parameters in 2 different ways

Setting global parameters and headers

var handler = Dapi.create({
  environment: Dapi.environments.sandbox, //or .production
  //... other configuration params
  clientHeaders: {"MY_CUSTOM_HEADER":"1234"},
  clientBody: {"MY_CUSTOM_BODY_PARAM":"xyz"},
  //... other configuration params
)}

You can retrieve the values on the backend side.

  • The header will show up as MY_CUSTOM_HEADER: "1234" in all request headers.
  • The body parameter will show up as UserExtraBody:{MY_CUSTOM_BODY_PARAM:"xyz"} in all request bodies.

Note! The body parameters are wrapped in an UserExtraBody object

Setting endpoint level parameters and headers

onSuccessfulLogin: function(bankAccount) {
    ba = bankAccount; 
    ba.data.getIdentity({"MY_CUSTOM_HEADER":"1234"},{"MY_CUSTOM_BODY_PARAM":"xyz"})
      .then((identityResponse)=>{
      	// response handling 
        }
    })
  },

You can retrieve the values on the backend side only for this specific endpoint.

  • The header will show up as MY_CUSTOM_HEADER:"1234" in the request headers.
  • The body parameter will show up as MY_CUSTOM_BODY_PARAM:"xyz" in the request body.

🚧

Using both methods at the same time?

Can both the global parameter and the endpoint level parameter have the same key value? In these cases, the specified endpoint level value takes precedence over the global value.

Web SDK Reference

Data Endpoints

Get Identity

Access the user's identity on this bank account. More info on this endpoint can be found here.

ParameterTypeDescription
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.data.getIdentity()
       .then(identityResponse => {
          if(identityResponse.status === "done") {
            console.dir(identityResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(identityResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Get Accounts

Access the sub-accounts of a bank account and balance for corresponding accounts.

ParameterTypeDescription
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.data.getAccounts()
    .then(accountsResponse => {
          if(accountsResponse.status === "done") {
            console.dir(accountsResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(accountsResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Get Cards

Access the credit cards associated with this bank account

ParameterTypeDescription
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.data.getCards()
    .then(cardsResponse => {
          if(cardsResponse.status === "done") {
            console.dir(cardsResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(cardsResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Get Account Transactions

Access the transactions of a given sub account. More info on this endpoint can be found here.

ParameterTypeDescription
accountIDStringThe id of a particular sub account.

Note: You can get this from calling getAccounts function
fromDateStringThe start date of the transactions you want to get. It needs to be in the YYYY-MM-DD format
toDateStringThe end date of the transactions you want to get. It needs to be in the YYYY-MM-DD format
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.data.getTransactions(accountID, "2020-01-01", "2020-02-01")
    .then(transactionsResponse => {
          if(transactionsResponse.status === "done") {
            console.dir(transactionsResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(transactionsResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Get Card Transactions

Access the transactions of a given credit card. More info on this endpoint can be found here.

ParameterTypeDescription
cardIDStringThe id of a particular card

Note: You can get this from calling getCards function
fromDateStringThe start date of the transactions you want to get. It needs to be in the YYYY-MM-DD format
toDateStringThe end date of the transactions you want to get. It needs to be in the YYYY-MM-DD format
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.data.getCardTransactions(cardID, "2020-01-01", "2020-02-01")
    .then(transactionsResponse => {
          if(transactionsResponse.status === "done") {
            console.dir(transactionsResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(transactionsResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Metadata Endpoints

Get Accounts Metadata

Access the bank's metadata object. More info on this endpoint can be found here.

ParameterTypeDescription
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.metadata.getAccounts()
     .then(metadataAccountsResponse => {
          if(metadataAccountsResponse.status === "done") {
            console.dir(metadataAccountsResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(metadataAccountsResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Payment Endpoints

Transfer Autoflow

Send money to a particular beneficiary. More info on this endpoint can be found here.

ParameterTypeDescription
transferObjectTransfer Object
clientHeadersObjectOptional
clientBodyObjectOptional

Transfer Object Schema

ParameterTypeDescription
senderIDStringThis is the id of a particular sub account. You can get this from calling getAccounts function.
beneficiaryObjectIts params can be found
here.
amountNumberThis is the amount you want to send.
remarksStringThis is the id you can use to track the transaction.

Example

var transfer = {
    senderID: "",
    amount: 0,
    remarks: "",
    beneficiary: {
      name: "Mohammad Omar Amr",
      nickname: "Mohammad Omar LIV",
      iban: "DAPIBANKAELIV1619116273261987517393",
      accountNumber: "1619116273261987517393",
      swiftCode: "DAPIBANK_AE_LIV",
      address: {
          line1: "Maryam Street",
          line2: "Abu Dhabi",
          line3: "United Arab Emirates"
      },
      country: "AE",
      branchAddress: "Dubai Mall",
      branchName: "Main Branch"  
    }
  }
  ba.payment.transferAutoflow(transfer)
    .then(transferResponse => {
          if(transferResponse.status === "done") {
            console.dir(transferResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(transferResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

📘

Note

Note: If you are not going to use the transfer autoflow endpoint, then you would have to handle the bank exceptions yourself. Dapi recommends you to use the transfer autoflow endpoint instead.

Get Beneficiaries

Access the bank account's beneficiaries array. More info on this endpoint can be found here.

ParameterTypeDescription
clientHeadersObjectOptional
clientBodyObjectOptional

Example

ba.payment.getBeneficiaries()
    .then(benefResponse => {
          if(benefResponse.status === "done") {
            console.dir(benefResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(benefResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Create Beneficiary

Create a beneficiary on this endpoint. More info on this endpoint can be found here.

ParameterTypeDescription
beneficiaryObjectBeneficiary Object. Its params can be found here
clientHeadersObjectOptional
clientBodyObjectOptional

Example

var beneficiary = {
      name: "Mohammad Omar Amr",
  		nickname: "Mohammad Omar Liv",
      iban: "DAPIBANKAELIV1619116273261987517393",
      accountNumber: "1619116273261987517393",
      type: "local",
      swiftCode: "DAPIBANK_AE_LIV",
      address: {
          line1: "Maryam Street",
          line2: "Abu Dhabi",
          line3: "United Arab Emirates"
      },
      country: "United Arab Emirates",
      branchAddress: "Dubai Mall",
      branchName: "Main Branch"  
  }
  ba.payment.createBeneficiary(beneficiary)
    .then(benefResponse => {
          if(benefResponse.status === "done") {
            console.dir(benefResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(benefResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Create Transfer

Send money to a particular beneficiary. More info on this endpoint can be found here.

ParameterTypeDescription
TransferObjectTransfer Object. Its params can be found here
clientHeadersObjectOptional
clientBodyObjectOptional

Transfer Object Schema

ParameterTypeDescription
senderIDStringThis is the id of a particular sub account. You can get this by calling the getAccounts function.
receiverIDStringThis is the id of a particular sub account. You can get this by calling the getBeneficiaries function
accountNumberStringThis is the accountNumber of a new beneficiary you want to send money to. Some banks require it
ibanStringThis is the iban of a new beneficiary you want to send money to. Some banks require it
amountNumberThis is the amount you want to send
remarksStringThis is the id you can use to track the transaction.

Example

var transfer = {
    senderID: "ntV7rbYoexYaGDRfLCAo8vw1xXgu2VaXXtqvNoMU0sfy6aNErfUEGMD+P6lAlkzu/GKxPeoef7d7eNoxlHKyRw==",
    receiverID: "kSSWuq7RXww1VZpvF05KBfeiQxr8nb/uHQ35ZqSmhnp2gVoqZ7+DCnI/zRLzcg32myS/e8BLPhMJaT7mJ3z9Uw==",
    accountNumber: "1619116273261987517393",
    name: "Mohammad Omar Amr",
    iban: "DAPIBANKAELIV1619116273261987517393",
    amount: 10,
    remarks: "",
  }
  
  ba.payment.createTransfer(transfer)
    .then(transferResponse => {
          if(transferResponse.status === "done") {
            console.dir(transferResponse)
          } else {
            console.error("API Responded with an error")
            console.dir(transferResponse)
          }
        })
        .catch(error => {
            console.dir(error)
        })

Caching

Since the Web SDK does support local caching, you can get access to all the connected accounts on a particular clientUserID.

📘

Note

The clientUserID needs to be unique per user on your app. Otherwise, you will get access to all the bank accounts connected in that browser.

const bankAccounts = Dapi.getCachedBankAccounts("CLIENT_USER_ID")
const bankAccountParams = bankAccounts.map(bankAccount => bankAccount.flush())
// You can save this bankAccountParams on your server side

After you got access to these bankAccounts, you can optionally save them on your server for later use. For future initialization, you can get these from your server and initialize them one by one again.

//get bankAccountParams from your server
//initialize DapiBankAccount based on a bankAccountParam
var dapiBA = new DapiBankAccount(bankAccountParams[0])

You can then call the data and payment endpoints on the dapiBA variable.

Launch in Production Checklist

This section highlights best practices and recommendations to help you achieve the best user experience with Dapi integration.

👍

ClientUserID configuration

ClientUserID is used to distinguish between different users on the same device. The value for ClientUserID needs to be set by you. We recommend setting clientUserID to your actual user ID that you use to distinguish between users. You should update the clientUserID once the user logs out and another user logs in.

Why? clientUserID is used to keep track of the bank connections in the cache.

👍

Bank IDs

If your logic is relying on bankIDs, then note that sandbox and production bankIDs are different. For example, ADCB:

  • Sandbox: DAPIBANK_AE_ADCB
  • Production: ADCBAEAA

👍

Displaying error messages

Web SDK does not support automatic displaying of error messages for Data and Payment API endpoints. Depending on your flow, we suggest setting up a way to communicate the occurrence of an error to the user.

You can determine the occurrence of an error in the .catch block.

ba.data.getCards()
 .then(cardsResponse => {})
 .catch(error => {console.dir(error)})

👍

Loading screens

Web SDK does not support showing loading screens between Data and Payment API calls. We suggest setting up a loading background for the SDK, so that the user would understand there is an operation going on.

You can leverage the onAuthModalOpen and onAuthModalSubmit callbacks if needed.

👍

Caching set-up

Caching for the Web SDK can work in 2 different ways:

  • By default, we offer browser level caching where the linked bankAccount objects are kept in the browser cache and can be retrieved with the corresponding clientUserID. Unlike on mobile SDKs, this cache is temporary and depends on the active session of the user.
  • For a more permanent solution and a better user experience we recommend storing the object from the cache on your backend alongside a unique identifier for the user. This will allow you to use the cache regardless of the browsers and sessions used.

👍

INVALID_CREDENTIALS or INVALID_CONNECTION

NB! Only applicable if you are using caching

When receiving this error, the bank connection should be abandoned. Depending on your flow, you can do one or multiple of those steps:

  • Delete the connection by deleting the bankAccount object used
  • Prompt the user with Connect screen again by creating a new var dapi = Dapi.create({...}) object and calling dapi.open()

Why? This error indicates that the user has updated their online banking credentials. This means that also the linked bank account details should be updated. If your application continues to use outdated details, it may result in blocking the user's banking account.

👍

Do you have internal timeouts?

Do you usually have a default timeout for the requests going out of your application or server? It is possible that resolving a request with the bank can take longer. Dapi has an internal timeout at

  • 240 seconds for payment/transfer/autoflow and wire/transfer/autoflow
  • 120 seconds for all other endpoints

Having a shorter timeout on your end can result in 504 errors.

Additional checklist for Data API

Only applicable if you are querying for transaction histories

👍

Transaction ranges

Each bank supports a different range of transactions.

You can retrieve the supported ranges from ba.metadata.getAccounts()

For expected response see: Metadata API. Refer to the transactionRange parameter.

Additional checklist for Payment API

👍

Special characters

Please double-check that you are only passing in alpha-numeric values in the beneficiary information. Including special characters in any of the fields will result in errors later on.

👍

BENEFICIARY_COOL_DOWN_PERIOD

Make sure you have handled the beneficiary cooldown period. Receiving this error means that beneficiary activation will take time from the bank side and the user must wait for the cooldown period to end before attempting the transfer again.

The exact time taken varies based on the user's bank. You can get the time take for the beneficiary to be active for any bank by using getAccountsMetaData API. You can for example use it to schedule a notification for the user to send money again when the beneficiary is activated.

👍

Do you need to reconcile the transfers?

If yes, make sure you are leveraging the remarks field in payment/transfer/autoflow or payment/transfer/create.

The remark field can hold any value set by you. It would be useful to use a value that uniquely identifies the transfer on your application side.

In order to be sure that the entire value can be used, we recommend keeping the remarks shorter than 15 characters.

NB! No special characters are allowed in the remarks field

Production Access

Once you have completed the above checklist here are the 2 simple steps to move your application from Sandbox to Production.

1. AppKey Permissions

Contact the Dapi team to give your existing appKey permission to make calls in our Production environment.

2. Change the Dapi.environments variable to .production

Congratulations, you are all set with your Dapi integration!