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.
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
.
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")
}
});
Parameter | Type | Description |
---|---|---|
environment | String | Dapi environment - Sandbox or Production |
appKey | String | Your app key from the dashboard |
countries | String[] | You can pass string of countries with country code eg AE, SA, US etc |
bundleID | String | The bundle id you injected on the dashboard |
clientUserID | String | This user's unique id on your app |
isCachedEnabled | Boolean | Setting this to true makes the sdk inject the bank account object in browser cache. Note: Set it to true only on trusted browsers |
isExperimental | Boolean | Show experimental banks if necessary |
clientHeaders | Object | If you want to inject additional headers in the calls |
clientBody | Object | If you want to inject additional body params in the calls |
onSuccessfulLogin | Function | Callback function that sends back a bankAccount object on successful login |
onFailedLogin | Function | Callback function that sends back the error object if an error happened during user login process |
onReady | Function | Callback function called when the widget has successfully loaded |
onExit | Function | Callback function called when the user closes the the SDK widget (exits the flow without completing it) |
onAuthModalOpen | Function | Callback function called when the MFA authentication modal opens up |
onAuthModalSubmit | Function | Callback 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.
Parameter | Type | Description |
---|---|---|
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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
Parameter | Type | Description |
---|---|---|
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
accountID | String | The id of a particular sub account. Note: You can get this from calling getAccounts function |
fromDate | String | The start date of the transactions you want to get. It needs to be in the YYYY-MM-DD format |
toDate | String | The end date of the transactions you want to get. It needs to be in the YYYY-MM-DD format |
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
cardID | String | The id of a particular card Note: You can get this from calling getCards function |
fromDate | String | The start date of the transactions you want to get. It needs to be in the YYYY-MM-DD format |
toDate | String | The end date of the transactions you want to get. It needs to be in the YYYY-MM-DD format |
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
transfer | Object | Transfer Object |
clientHeaders | Object | Optional |
clientBody | Object | Optional |
Transfer Object Schema
Parameter | Type | Description |
---|---|---|
senderID | String | This is the id of a particular sub account. You can get this from calling getAccounts function. |
beneficiary | Object | Its params can be found here. |
amount | Number | This is the amount you want to send. |
remarks | String | This 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.
Parameter | Type | Description |
---|---|---|
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
beneficiary | Object | Beneficiary Object. Its params can be found here |
clientHeaders | Object | Optional |
clientBody | Object | Optional |
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.
Parameter | Type | Description |
---|---|---|
Transfer | Object | Transfer Object. Its params can be found here |
clientHeaders | Object | Optional |
clientBody | Object | Optional |
Transfer Object Schema
Parameter | Type | Description |
---|---|---|
senderID | String | This is the id of a particular sub account. You can get this by calling the getAccounts function. |
receiverID | String | This is the id of a particular sub account. You can get this by calling the getBeneficiaries function |
accountNumber | String | This is the accountNumber of a new beneficiary you want to send money to. Some banks require it |
iban | String | This is the iban of a new beneficiary you want to send money to. Some banks require it |
amount | Number | This is the amount you want to send |
remarks | String | This 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
andonAuthModalSubmit
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 correspondingclientUserID
. 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 callingdapi.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
andwire/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 inpayment/transfer/autoflow
orpayment/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
Dapi.environments
variable to .production
Congratulations, you are all set with your Dapi integration!
Updated about 1 year ago