Skip to main content

Webhook - Bank

This section explains how to receive callbacks when the verification status of a user's linked bank account changes. The bank webhook is common to On-Ramp and Off-Ramp — the same callback and payload apply to both.

Configuring Webhook​

Webhooks are configured in the merchant dashboard.

Inside the Setup section, the bank webhook URL can be added and modified under the Callback URL's section.

info

The bank webhook has its own callback URL. It is separate from the On-Ramp and Off-Ramp order webhook URLs, so make sure a URL is configured for it. If no bank webhook URL is configured for your account, no bank webhook is sent.

Webhook Security & HMAC Verification​

⚠️ Always verify webhook signatures

Your webhook endpoint is a public URL — anyone on the internet can send a POST request to it. Without verification, a malicious actor could forge a fake "account successfully added" event and trick your system into treating an unverified bank account as verified.

How it works​

Every bank webhook carries a signature in the X-Onmeta-Signature request header. It is an HMAC-SHA256 of the exact request body, generated with your API Secret (the Client Secret from the merchant dashboard) and hex-encoded.

HMAC-SHA256(apiSecret, rawRequestBody) === X-Onmeta-Signature
Verify against the raw body

Compute the signature over the raw request body exactly as received, before parsing it. Re-serialising a parsed JSON object can change spacing or escaping and produce a different hash.

Verification Example​

const crypto = require('crypto');
const express = require('express');
const app = express();

// Keep the body raw so the HMAC is computed over the exact bytes received
app.post('/onmeta/bank-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const receivedSignature = req.headers['x-onmeta-signature'];
const apiSecret = process.env.ONMETA_API_SECRET; // from merchant dashboard

const expectedSignature = crypto
.createHmac('sha256', apiSecret)
.update(req.body) // Buffer containing the raw body
.digest('hex');

const valid =
receivedSignature &&
receivedSignature.length === expectedSignature.length &&
crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(receivedSignature));

if (!valid) {
return res.status(401).send('Invalid webhook signature');
}

const event = JSON.parse(req.body.toString('utf8'));
// handle event ...
res.sendStatus(200);
});
💡 Where to find your API Secret

Log in to your Merchant Dashboard → Setup → API's Setup. Use the Client Secret (not the Client ID) as the HMAC secret. Keep this value private and never expose it in client-side code or public repositories.

Bank Verification Update​

POST{{configured_bank_webhook_url}}

This callback is triggered each time the verification result of a bank account linked by your user is recorded — including when the account is verified successfully. It uses the configured bank webhook URL and sends the details in the POST body.

info

Make sure you have firewall rules configured to allow receiving the webhook body, otherwise your firewall might block our webhook requests.

Your endpoint should respond with a 2xx status within 5 seconds.

Headers​

HeaderTypeRequiredDescription
Content-TypestringYesapplication/json; charset=utf-8
X-Onmeta-SignaturestringYesHMAC-SHA256 signature of the raw request body, for webhook verification

Request Body Parameters​

ParameterTypeRequiredDescription
namestringYesName of the user as per KYC
emailstringYesEmail of the user who linked the bank account
bankDetailsobjectYesDetails of the bank account the event is about
bankDetails.statusstringYesVerification status of the bank account: SUCCESS, PENDING, FAILED or MANUAL
bankDetails.referenceNumberstringYesReference number of the bank account. Same value returned by the Link Bank Account API — use it to match the event to your request
bankDetails.accountNumberstringNoBank account number
bankDetails.accountNamestringNoAccount holder name
bankDetails.ifscstringNoIFSC code of the bank branch
bankDetails.branchAddressstringNoBranch address
bankDetails.tenantIdstringNoYour merchant (tenant) ID
messagestringYesHuman-readable description of the event. See Webhook Events
reasonstringYesReason for the status. See Webhook Events for the value sent with each status
note

Fields marked No are left out of bankDetails when they have no value. reason is always present.

Code Examples​

curl --location -g --request POST '{{configured_bank_webhook_url}}' \
--header 'Content-Type: application/json; charset=utf-8' \
--header 'X-Onmeta-Signature: string' \
--data-raw '{
"name": "Ravi Kumar",
"email": "ravi@example.com",
"bankDetails": {
"accountNumber": "123456789012",
"accountName": "RAVI KUMAR",
"ifsc": "HDFC0001234",
"branchAddress": "MG Road, Bengaluru",
"referenceNumber": "a1b2c3d4-1234-5678-abcd-ef1234567890",
"tenantId": "65ef27a598b805cba22fb3b5",
"status": "SUCCESS"
},
"message": "account successfully added",
"reason": "account successfully added"
}'

Example Webhook Request​

{
"name": "Ravi Kumar",
"email": "ravi@example.com",
"bankDetails": {
"accountNumber": "123456789012",
"accountName": "RAVI KUMAR",
"ifsc": "HDFC0001234",
"branchAddress": "MG Road, Bengaluru",
"referenceNumber": "a1b2c3d4-1234-5678-abcd-ef1234567890",
"tenantId": "65ef27a598b805cba22fb3b5",
"status": "SUCCESS"
},
"message": "account successfully added",
"reason": "account successfully added"
}

Webhook Events​

The bank webhook has a single payload shape. The event is identified by bankDetails.status and the matching message:

#StatusMessageDescription
1SUCCESSaccount successfully added

The bank account was verified and added to the user's account. The user can now use it for orders. reason is account successfully added.

2PENDINGaccount verification pending

Verification is still in progress and the account cannot be used yet. You can check the latest status using the Bank Verification Status API. reason is the verification provider's message when there is one (for example failed to verify account, please retry after some time), otherwise account verification pending.

3FAILEDaccount/pan name mismatch. ACCOUNT HOLDER NAME: <name>

Verification failed because the account holder name does not match the user's KYC name. The message ends with the account holder name returned by the bank. The user has to link a bank account in their own name. reason is kyc name mismatch. ACCOUNT HOLDER NAME: <name>, kycNames: <names>, where kycNames lists the user's KYC names (comma-separated) that the account holder name was compared against.

4MANUALpending for manual verification

The account holder name could not be matched automatically with the user's KYC name, so the account has been sent to Onmeta for manual review. The account cannot be used until the review is complete. reason has the same format as for FAILED.

See Sample Responses for a full payload for each status.

Handling the Webhook​

  • Match on referenceNumber. It is the same reference number returned when the bank account was linked, so use it to find the user's request on your side.
  • Branch on bankDetails.status. Treat message as display text, not as a value to parse. For FAILED, the message includes the account holder name, so it is different on every event.
  • Expect more than one webhook per bank account. The status of an account can change over time, for example from PENDING or MANUAL to SUCCESS or FAILED; always apply the latest event.
  • Make your handler idempotent. The same status can be delivered more than once for a bank account, for example when a verification result is reported again. Key on referenceNumber and bankDetails.status and ignore events you have already processed.
  • Respond quickly. Return a 2xx status within 5 seconds and do any heavy processing asynchronously.