There are two types of webhooks that are used to communicate with the Finswich platform. The outgoing webhook is used to communicate with a merchant when their users want to send funds to another platform (Outgoing Transactions) while the incoming webhook is used when a user from another platform transacts with their own users (Incoming transactions).
FOR OUTGOING TRANSACTIONS
An outgoing transaction is a kind of transaction in which the users on your platform intend to send money on your wallet to other wallets, banks in other countries.
BEFORE INTEGRATION - DO THIS
Before you begin any integration you need to add an outgoing webhook where all request between Finswich & your application will be posted. See Below for Image reference
TRANSACTION FLOW
To perform an outgoing transaction on Finswich, there are 4 events that has to take place.
1. Retrieve User Event - The Finswich platform will contact your application via the outgoing webhook to get your user's data.
The data below is sent (Request body) to your outgoing webhook registered on Intrapay.
{"event":"RETRIEVE_USER","data":{"user_reference":"testappemcodeng001","mode":"LIVE"}}//The user_reference is the user reference provided via the checkout //mode decides if you are testing "TEST" or have gone live "LIVE"
Here is a sample of the code on your backend
// check if the user reference set from finswich is in the data field of req.bodyif (body.event ==='RETRIEVE_USER') {//! TODO: send the user data to FinSwitch// check if there is data field in the req.bodyif (body?.data) {// check if the user reference in the data field of req.bodyif (body.data.user_reference) {constuser_reference=body.data.user_reference;// retrieve the user from my third-party database using the unique user referenceconstuser=awaitthis.user_repository.FindById( user_reference, ['-permissions','-settings','-recovery_keys','-contigency_lock_expires','-contigency_level','-verification_code', ], );//: send user data to FINSWITCHres.send ({ status:'success', message:'User Data sent', data: { first_name:user.firstname, last_name:user.lastname, email:user.email, user_reference: user_reference, country:user.country, phone_number:user.phone, }), }; } } }
2. Transaction Initiate - Once the Wallet Checkout is opened, the transaction Initiate is trigger when the user want to perform a transaction and they need to authorize the transaction as shown below.
Req body sent from FInswich to your outgoing webhook
Here is a code sample to handle the transaction initiate event on your end
if (body.event ==='TRANSACTION_INITIATE') {//!: MAKE SURE THE USER IS VALID AND HAVE THE REQUIRE BALANCE AND AUTHORIZATION//! consider debiting your wallet hereconstdebit_model= { txn_reference:body.data.txn_reference, user_reference:body.data.user_reference, amount:body.data.amount, currency:body.data.currency, };// Retrieve user balance to confirm if they have enough money to continue transactionconstwallet=awaitthis.wallet_repository.FindByUserId(debit_model.user_reference, );// check if the balance actually existsif (!wallet) {thrownewnotFoundException(en['wallet-not-found']); }// check if the balance is enough for transactionif (!wallet.balance <debit_model.amount) {thrownewnotFoundException(en['insuficient-balance']); }// create a transaction log on third-party systemawaitthis.finswich_txn_logs_repository.CreateTxnLogs({ txn_reference:debit_model.txn_reference, amount:debit_model.amount, currency:debit_model.currency, user:debit_model.user_reference, wallet_id:wallet.id, type:'outgoing', status:'pending', });res.send({ status:'success', message:'Txn Permitted', data: { user_category:'dfa292a668bc4109af10cd5bddc8dab6',// this is obtained from the policy on ur finswitch account }, }); }
3. Send OTP - At the initiation of the transaction, an OTP request is sent to your application for you to generate an OTP for your user to that they can authorize the payment. Once the user submit the OTP, we will combine the data with the Complete Transaction Payload that will be sent to your application.
{"event":"SEND_OTP","data":{"otp_message":"You are about to make a payment of 10.0 sar to Ahmed Emcode on SaudiApp platform from your EmcodeCashApp wallet.\nUse this One Time Password to verify your transaction. _OTP_.","txn_reference":"FIN-DXVb1CpC6jnTXkqJ1682607236", user_reference:"","mode":"LIVE"}}/ Some code
Here is a sample code on your end for the send OTP event
if (body.event ==='SEND_OTP') {//! SEND CUSTOM OTP MESSAGE TO UR USERS VIA SMS, VOICE, EMAIL OR WHATSAPP//TODO: InitiateOTP fxn// await this.InitiateOTP(body.data?.user_reference)// create code for a transaction on third-party systemconst { code } =awaitthis.finswich_txn_logs_repository.CreateCode(body.data?.txn_reference, );constuser=awaitthis.user_repository.FindById(body.data?.user_reference, );// Send custom otp message to usersecurityConfig.NO_PROD!=true? taskQueue.add('send_sms', { to:user.phone, code: code, }, { removeOnComplete:true, removeOnFail:true }, ).catch((error) => {this.error_service.CreateErrorLog(error, req);return; }):console.log(code);res.send({ status:'success', message:'OTP SENT!', }); }
4. Complete Transactions - Finswich will send you the payload request with the OTP. You have the sole responsibility to verify the OTP before you issue an authorization for Finswich to honor the request.
NOTE
a. User Category according to the policy you created on your Finswich dashboard will apply for every outgoing transaction. I.e If a user’s category can only send $100 per day, that limit will apply for everything such user can do on the Finswich Checkout.
b. If you do not create a user category or any policy in your dashboard, The Finswich default wallet policy and category will be imposed all your users.
c. Once the request is honored, we will place a debit to your wallet, it’s important that you debit your own users on their wallet on your application before you respond Finswich with a Success Status.
d. We advised that you create your user categories and policy before integration and according to the AML policy of your organization
if (body.event ==='COMPLETE_TRANSACTION') {//! if there is an errorif (!('data'in body)) {//: rejectthrownewbadRequestException(en['transaction-error'],400, { user_category:'', }); }constget_data=body.data;//const body = body//const hash_payload = req.headers["x-finswich-signature"]// const can_give_value = await hl.FinswichHashMessageAuthCode(body, secret, hash_payload)// const can_give_value = await hl.FinswichHashMessageAuthCode(body, secret, hash_payload)//! extract payload constb2b_payload= { txn_reference:get_data.txn_reference, auth_code:get_data.otp, };// Verify the otp on third-party systemconstdebit_log=awaitthis.VerifyCode(get_data.otp,get_data.txn_reference, );// retrieve the userconstuser=awaitthis.user_repository.FindById(debit_log.user, [], ['app'], );// retrieve the owner of the appconstmerchant=awaitthis.merchant_repository.FindById(user.app.merchant, );if (get_data.mode =='LIVE') {//!: make sure is valid on finswich before giving value to the user in your wallet system//! please see a sample of our hmac for reference// retrieve third parties secret key and public keyconstsecret=this.decrypt_finswich_key(merchant.finswich_secret_key, ); // this is the secret key you downloaded earlierconstpublic_key=this.decrypt_finswich_key(merchant.finswich_public_key, ); // this is the public key you downloaded earlier// sign the payload which would be sent back to finswichconsthash_payload=awaitthis.FinswichHashMessageAuthCode( b2b_payload, secret, );//! make the b2b wallet to wallet transfer nowconstget_response=awaitthis.AuthorizeFinswich( public_key, hash_payload,b2b_payload.txn_reference,b2b_payload.auth_code, );console.log('\x1b[41m%s\x1b[0m','#RESPONSE FROM FINSWICH B2B WALLET SERVER', get_response, );if ('status'in get_response) {if (get_response.status ==='success') {//: debit walletawaitthis.wallet_repository.DeductBalance(debit_log.wallet_id,debit_log.amount, );awaitthis.wallet_repository.UpdateById(debit_log.wallet_id, {}, { total_outgoing:debit_log.amount }, );//! update txn statusawaitthis.finswich_txn_logs_repository.UpdateById(debit_log.id, { status:'success', });return { status:'success', message: en['transaction-completed'], data: { user_category:'', }, }; } } }thrownewbadRequestException(en['transaction-error'],400, { user_category:'', }); }decrypt_finswich_key(encrypted_text) {constalgorithm='aes-256-cbc';constdecipher=crypto.createDecipher( algorithm,securityConfig.ENCRYPTION_KEY, );let decrypted =decipher.update(encrypted_text,'hex','utf-8'); decrypted +=decipher.final('utf-8');return decrypted; } async VerifyCode(code, txn_reference) {try {let debit_logs_exist =awaitthis.finswich_txn_logs_repository.FindByFilter( { txn_reference, }, ['+verification_code'], );if (!debit_logs_exist) {thrownewError(en['wallet-not-found']); }consthashedCode=crypto.createHash('sha256').update(code).digest('hex');if (debit_logs_exist.verification_code === hashedCode) {constupdate_data= { verification_code:null, verification_expires:null, };constdebit_log=awaitthis.finswich_txn_logs_repository.UpdateById(debit_logs_exist.id, update_data, );return debit_log; }thrownewauthException(en['token-verification-error']); } catch (error) {console.log(error);thrownewauthException(error.message); } } async FinswichHashMessageAuthCode(body, secret_key) {let bdy =typeof body =='object'?JSON.stringify(body) : body;constcrypto=require('crypto');constverify_hmac= crypto.createHmac('sha512', secret_key).update(bdy).digest('hex');return verify_hmac; } async AuthorizeFinswich(public_key, signature, txn_ref, auth_code) {returnnewPromise((resolve, reject) => {constrequest=require('request');constoptions= { method:'POST', url:"https://finswichstaging.instance.exchange/api/v1/app-transfer/wallet", headers: {'x-finswich-signature': signature,'x-public-key': public_key,'content-type':'application/json', }, body: { txn_reference: txn_ref, auth_code }, json:true, };request(options,function (error, response, body) {if (error) {reject(error); }resolve(body); }); }); }
FOR INCOMING TRANSACTIONS/FUNDING
An incoming transaction is a kind of transaction where users on your platform receives money between into wallet from other wallets, banks or other countries or a funding activity. This is what we refer to as an incoming transactions.
BEFORE INTEGRATION - DO THIS
Before you begin any integration you need to have created an incoming URL endpoint where all request between Finswich & your application will take place for interactions and notifications of incoming transactions. See Below for Image reference
TRANSACTION FLOW EVENTS
User Category: this is used to ensure that the recipient user has the right policy setup to receive the incoming transaction
Credit Transactions Events -Once there is a credit to your Finswich wallet, the notifications will be posted to your incoming webhook URL. you can use the data to update your user’s wallet balance on your application.