Build with the BankMe APIs
Send money from your BankMe wallet to any bank account (IMPS or NEFT) or UPI ID with one HTTPS call, and get a signed webhook when the payout settles.
referenceId never pays twice.What you can build
| API | What it does | Start here |
|---|---|---|
| Payout | Send money from your wallet to any bank account or UPI ID | Create payout |
| Verification | PAN, GSTIN, bank account, company, licence and more in one call | Verify a document |
| BBPS | Electricity, water, gas, DTH and other bill payments | Bill categories |
| DMT | Domestic money transfer to any bank account | Check a sender |
| AEPS | Balance, statement and cash withdrawal with Aadhaar and fingerprint | Balance enquiry |
All of them share the same authentication, the same sandbox and the same wallet. Test everything in the sandbox first.
How it works
- BankMe issues you a sandbox key (
pk_...andsk_...). Production keys follow after your integration is approved. - Your server calls Create payout with a unique
referenceId. - BankMe debits your wallet and sends the money. The result comes back in the response or as a webhook.
- If a call times out, ask for the result with Payout status. Never retry with a new reference.
API access is issued by BankMe. Each key is tied to one wallet and one payout route, with its own per-payout and per-day limits. Contact your BankMe account manager to get your keys.
Environments
Two environments, same paths and same code.
| Environment | Base URL | Does money move? |
|---|---|---|
| Sandbox | https://sandbox.bankme.co.in | No. Results are simulated and your wallet is never debited. |
| Production | https://api.bankme.co.in | Yes. Your wallet is debited and the beneficiary is paid. |
Keys are not shared between environments. A sandbox key does not work on production, and the other way round. Every response carries an environment field so you can confirm where you are. Use the toggle in the code panels on this page to switch the base URL in every example.
Authentication
Every request carries your key id and secret in two headers.
| Header | Required | Value |
|---|---|---|
x-api-key | Yes | Your key id, starts with pk_ |
x-api-secret | Yes | Your secret, starts with sk_. Shown once when the key is created. |
Content-Type | On POST | application/json |
Keep the secret on your server. Never put it in a mobile app, a web page or a public repository. If it leaks, ask BankMe to rotate it straight away.
IP allowlist
Production keys only work from the server IP addresses you give BankMe. Calls from any other address are rejected. Call GET /v1/ip from your server to see the address BankMe sees.
Rate limit
120 requests per minute per key.
Create payout
Send money to a bank account or UPI ID.
Request body
| Field | Type | Notes |
|---|---|---|
referenceId | string | Required. Your own unique id for this payout. It is the idempotency key. |
amount | integer | Required. Whole rupees, minimum 100. |
beneficiary | object | Required. Bank or UPI, see below. |
remarks | string | Optional. Up to 60 characters. |
speed | string | Optional. "standard" (default) or "instant". See Standard and instant payouts below. |
Beneficiary: bank account
| Field | Notes |
|---|---|
type | "bank" |
name | Account holder name. Letters, spaces and dots only. |
accountNumber | 9 to 18 digits |
ifsc | Valid IFSC code, for example HDFC0001234 |
mode | "IMPS" (default) or "NEFT" |
Beneficiary: UPI
| Field | Notes |
|---|---|
type | "upi" |
name | Beneficiary name |
vpa | UPI ID, for example name@bank |
Response
| Field | Meaning |
|---|---|
payoutId | BankMe's id for the payout |
referenceId | Your reference, returned unchanged |
amount, fee, totalDebit | Rupees. totalDebit is amount plus fee. |
status | pending, processing, success or failed |
utr | Bank reference number, filled when the payout succeeds |
reason | Why a payout failed, empty otherwise |
speed | standard or instant |
instantCharge | Rupees. The extra charge for an instant payout, including GST. It is included in totalDebit. Zero for standard. |
scheduledFor | When a standard payout is due to be sent. Instant payouts are sent at once. |
environment | sandbox or production |
Standard and instant payouts
| Speed | What happens |
|---|---|
standard (default) | Your wallet is debited at once. The payout stays pending and is sent in the next BankMe batch, which is the next day or, where two batches a day apply, the next batch time. The time is in scheduledFor. No extra charge. |
instant | The payout is sent immediately. It carries an extra charge of Rs 0.15 per Rs 1,000 (minimum Rs 1) plus 18% GST. For example, an instant payout of Rs 10,000 adds Rs 1.77 to totalDebit. Your agreed rate can differ. |
Who can use instant. Instant payout opens when your account reaches Rs 1 crore of successful pay-in business. Before that, an instant request is refused with INSTANT_NOT_AVAILABLE and nothing is debited. Send a standard payout instead.
A speed value other than standard or instant is refused with INVALID_SPEED. In the sandbox you can test the instant request and its charge without the Rs 1 crore requirement.
Idempotency. If you send a referenceId that already exists, you get the original payout back with "duplicate": true. Nothing new is created or debited.
Debit and refund. A live payout debits amount + fee (plus the instant charge, if you ask for instant) from your wallet immediately. If the payout fails, the full debit is returned to your wallet. A request that fails validation never debits anything.
Payout status
Fetch a payout by your own reference, plus the delivery state of its webhook.
Use this when a request timed out and you do not know if it arrived, or when you want to confirm a payout without waiting for the webhook. The webhook object shows status, attempts and lastError for the delivery of this payout's result.
If you are unsure whether a create call reached BankMe, call this endpoint first. Do not create a new payout with a new reference.
Wallet balance
The current balance of the wallet your key pays from, in rupees.
Check it before large batches. In the sandbox the balance is virtual and is never debited.
Your IP address
Returns the public IP address BankMe sees for your request. No keys needed.
Run it from your production server, then give that address to BankMe to add to your allowlist.
Limits and fees
| Item | Value |
|---|---|
| Minimum per payout | Rs 100 |
| Maximum per payout | Up to Rs 2,00,000. Your key may have a lower limit. |
| Daily limit | Rs 5,00,000 by default, set per key. The day resets at midnight IST. |
| Fee | Slab based, from Rs 10 to Rs 100 per payout, GST included. The exact fee is returned in fee. |
| Rate limit | 120 requests per minute per key |
Status and error codes
Failed requests return a non-2xx status and JSON with "success": false, a code and a readable message.
| Code | When it happens |
|---|---|
AUTH_REQUIRED | Missing, wrong or disabled key or secret, or the call came from an IP that is not allowed. |
INVALID_AMOUNT | Amount is not a whole number, or is below Rs 100. |
INVALID_BENEFICIARY | A beneficiary field is missing or badly formatted. The message names the field. |
LIMIT_PER_TXN | Amount is above the per-payout limit. |
LIMIT_PER_DAY | The daily limit would be exceeded. The message shows what is left today. |
INSUFFICIENT_BALANCE | The wallet cannot cover amount plus fee. |
RAIL_UNAVAILABLE | The payout route for your key is not available right now. Try again later. |
OWNER_NOT_FOUND | The wallet owner for the key was not found. Contact BankMe. |
A request that fails validation never debits your wallet. For a timeout or a network error, check Payout status before doing anything else.
Create a payment
Collect a UPI payment from your customer through a BankMe payment link.
You create an order, then send your customer to the paymentUrl in the response. The customer pays on a BankMe hosted page. BankMe confirms the payment on its own side and tells you the result by webhook. You never handle the customer's UPI details.
Request body
| Field | Type | Notes |
|---|---|---|
referenceId | string | Required. Your unique id, 6 to 60 letters, numbers, - or _. It is the idempotency key. |
amount | number | Required. Rupees. Minimum Rs 10, and within your per-order and daily limits. |
customer | object | Optional. name and mobile (10 digit Indian number). |
redirectUrl | string | Optional. An https:// address where the customer returns after paying. |
note | string | Optional. Up to 80 characters. |
Response
| Field | Meaning |
|---|---|
orderId | BankMe's id for the order |
referenceId | Your reference, returned unchanged |
amount, fee, netAmount | Rupees. netAmount is amount minus fee. This is what reaches your wallet. |
status | pending, success, failed or expired |
paymentUrl | Send your customer here. Present only while the order is open. |
settlementStatus | Where the money is on its way to your wallet. See Settlement and activation. |
utr | Bank reference number, filled when the payment succeeds |
reason | Why a payment failed, empty otherwise |
environment | sandbox or live |
createdAt, paidAt | Timestamps. paidAt is empty until the payment succeeds. |
Example request
{
"referenceId": "ORDER_10045",
"amount": 499,
"customer": { "name": "Asha K", "mobile": "9876543210" },
"redirectUrl": "https://yourshop.example/thanks",
"note": "Order 10045"
}
Idempotency. If you send a referenceId that already exists, you get the original order back. No second order is created.
Do not trust the redirect. The customer returning to your redirectUrl does not prove payment. Confirm with the webhook or the status call before you deliver goods.
Errors
| Code | Meaning |
|---|---|
INVALID_REFERENCE | referenceId is not 6 to 60 letters, numbers, - or _ |
INVALID_AMOUNT | Below the minimum, or too small after charges |
INVALID_URL | redirectUrl is not a valid https:// address |
INVALID_INPUT | A field such as customer.mobile is not valid |
LIMIT_PER_TXN | Above your per-order limit |
LIMIT_PER_DAY | Your daily pay-in limit is reached |
NOT_ACTIVATED | Live pay-in is not activated for your account yet |
NOT_AVAILABLE | Pay-in is not available right now. Try again shortly. |
SERVER_ERROR | Something went wrong. Check the status with your referenceId before you retry. |
Payment status
Fetch an order by your own reference.
For an open live order, BankMe checks the latest result before it answers. Use this when a request timed out, when a webhook has not arrived, or when you want to confirm a payment yourself. An unknown reference returns NOT_FOUND.
If you are unsure whether a create call reached BankMe, call this endpoint first. Do not create a new order with a new reference.
Pay-in webhooks
BankMe tells your server when a payment succeeds or fails.
| Event | When |
|---|---|
payin.success | The customer's payment is confirmed |
payin.failed | The payment failed or the order expired |
The body is JSON: an event field plus the same fields as the order response. The headers are x-bankme-event, x-bankme-timestamp and x-bankme-signature. The signature is HMAC-SHA256 of timestamp + "." + body, using your webhook secret, in hex. The header value is sha256= followed by that hex. It is the same scheme as your payout webhooks, so use the same verification code from the Webhooks section.
Retries. If your server does not answer with a 2xx status, BankMe retries after 1, 5, 30, 120 and 720 minutes. The same event can arrive more than once, so treat your referenceId as the key and ignore repeats.
Your webhook address must be an https:// address on a public domain.
Settlement and activation
When you are paid
Money from your customer reaches BankMe first. Your wallet is credited with the netAmount only after BankMe has received it. Depending on how the payment was received, that is either instant or the next working day (T+1). Your settlementStatus follows it: pending, then settled.
Pay-in money always settles to your BankMe wallet. Payouts to your own bank account use the Payout API, to verified accounts only.
Chargebacks
If a customer disputes a payment, the amount is debited from your wallet and the order shows a chargeback. If the wallet is short, the shortfall is kept as a balance due and recovered from your next credits. You can see chargebacks and a daily reconciliation in your Partner Console.
Going live
| Step | Detail |
|---|---|
| 1. Test | Use the sandbox for at least 7 days and complete at least one successful test payment. |
| 2. Activate | One-time activation fee of Rs 15,000 plus 18% GST (Rs 17,700 in total). |
| 3. IP address | Add at least one server IP address to your allowlist. |
| 4. Production key | After BankMe verifies your activation payment, your production credentials are issued. |
Charges, limits and settlement timing can differ for each partner and change with volume. Your agreed rates are shown in your Partner Console.
Test pay-in in the sandbox
No real money moves in the sandbox.
Create an order with your sandbox key. The paymentUrl opens a test page with two buttons, one to simulate a successful payment and one to simulate a failed payment. Press one, then read the result with the status call or your webhook.
| You want | Do this |
|---|---|
| Success | Open the paymentUrl and choose the success button |
| Failure | Open the paymentUrl and choose the failed button. The result is failed. |
| Duplicate | Send the same referenceId twice |
| Above the limit | Send an amount over your per-order limit. The call is refused with LIMIT_PER_TXN. |
Going live. Live pay-in is refused with NOT_ACTIVATED until your activation is complete. Your sandbox days and test payments count towards the 7 day requirement.
Verify a document
One call to check a PAN, GSTIN, bank account, company, licence and more. The product id goes in the path.
Send the inputs for that product in the JSON body. The product list shows the product ids and the inputs each one needs.
Request body
| Field | Type | Notes |
|---|---|---|
(product inputs) | string | Required. The inputs for the product, for example pan, name and dob for pan_lite. |
referenceId | string | Optional. Your own unique id, 6 to 60 letters, numbers, - or _. Send it to make the call safe to repeat. |
Response
| Field | Meaning |
|---|---|
success | Whether the call was processed |
product | The product id you called |
result.status | success or failed |
result.data | The verified details. The fields depend on the product. |
result.code | On failure, for example NOT_FOUND when no record exists |
charged | Live only. The price, GST and total taken from your wallet. All zero when nothing is charged. |
callId | Live only. BankMe's id for the call |
sandbox | true on sandbox calls |
You pay only for a found record. The price plus 18% GST is held from your wallet first. If no record is found or the service is down, the full amount returns to your wallet at once and the response shows charged as zero.
Repeat safely. If you send a referenceId that already exists, you get the first result back with "repeated": true and are not charged again.
Test in the sandbox
| You want | Send |
|---|---|
| Success | Any input in the right format. The response has a sample record. |
| Failure | Make the first input end in 0000 or start with ZZZZ. The result is failed with code NOT_FOUND. |
Sandbox first. Use your sandbox key on https://sandbox.bankme.co.in. Results are simulated, your wallet is never debited and no real money moves. Sandbox responses carry "sandbox": true and "charged": 0.
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Verification products and inputs
Use the product id in POST /v1/verify/{product}.
| Product id | Name | Inputs | Production |
|---|---|---|---|
pan_fetch | Fetch PAN details | pan | Sandbox, live soon |
pan_lite | PAN verification (Lite) | pan, name, dob | Sandbox, live soon |
pan_adv | PAN verification (Advanced) | pan | Sandbox, live soon |
bank_pennyless | Bank account (Pennyless) | acct, ifsc | Sandbox, live soon |
bank_pennydrop | Bank account (Pennydrop) | acct, ifsc | Sandbox, live soon |
vpa_verify | UPI ID (VPA) | vpa, mobile, name | Sandbox, live soon |
mobile_vpa | Mobile to VPA | mobile | Sandbox, live soon |
gst_basic | GST verification (Basic) | gstin | Live |
gst_pan | Fetch GST with PAN | pan | Sandbox, live soon |
gst_adv | Advanced GST | gstin | Sandbox, live soon |
cin | CIN verification | cin | Live |
din | DIN verification | din | Sandbox, live soon |
itr | ITR compliance check | pan | Sandbox, live soon |
fssai | FSSAI licence | fssai | Live |
msme | MSME / Udyam | udyam | Live |
dl | Driving licence | dl, dob | Sandbox, live soon |
voter | Voter ID | epic | Sandbox, live soon |
passport | Passport application | passfile, dob | Sandbox, live soon |
name_match | Name match | name, name2 | Sandbox, live soon |
rc | Vehicle RC | rc | Sandbox, live soon |
echallan | Vehicle e-challan | rc | Sandbox, live soon |
employee | Employee detail | uan | Sandbox, live soon |
email | Email verification | email | Sandbox, live soon |
revgeo | Reverse geocoding | lat, lng | Sandbox, live soon |
ip | IP verification | ip | Sandbox, live soon |
Input formats
| Input | Format | Example |
|---|---|---|
pan | PAN, 10 characters | ABCDE1234F |
name, name2 | Letters, spaces, dots, apostrophes and hyphens, 2 to 80 characters | Rohan Kumar |
dob | YYYY-MM-DD | 1990-01-31 |
acct | 6 to 20 digits | 1234567890 |
ifsc | IFSC, 11 characters | HDFC0001234 |
mobile | 10 digit Indian number | 9876543210 |
vpa | UPI ID | name@bank |
gstin | GSTIN, 15 characters | 22AAAAA0000A1Z5 |
cin | CIN, 21 characters | U12345TN2020PTC123456 |
din | 8 digits | 01234567 |
fssai | 14 digits | 12345678901234 |
udyam | Udyam number | UDYAM-TN-00-0000000 |
dl | Driving licence number | TN3720200001234 |
epic | Voter ID number | ABC1234567 |
passfile | Passport file number, 10 to 16 characters | CH1234567890123 |
rc | Vehicle registration number | TN37AB1234 |
uan | 12 digits | 123456789012 |
email | Email address | name@example.com |
lat, lng | Decimal degrees | 11.0168, 76.9558 |
ip | IP address | 8.8.8.8 |
Products marked Live work on production keys once activated. The others return simulated data in the sandbox now and are switched on one by one. Price per call is on your partner rate card. Prices exclude 18% GST.
Bill categories
List the bill categories you can pay: electricity, water, gas, DTH, broadband and more.
Pay bills in four steps. Get the categories, pick the biller (operator), fetch the bill, then pay it. Each step has its own call below.
Sandbox first. Use your sandbox key on https://sandbox.bankme.co.in. Results are simulated, your wallet is never debited and no real money moves.
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Billers (operators)
List the billers in a category. Use the operatorCode when you fetch or pay a bill.
Query
| Field | Type | Notes |
|---|---|---|
category | string | Optional. A category id from the categories call. |
location | string | Optional. Narrow the list to a state or area. |
Fetch a bill
Look up the amount due for a customer account before you pay.
Query
| Field | Type | Notes |
|---|---|---|
operatorCode | string | Required. From the operators call. |
account | string | Required. The customer's account or consumer number with the biller. |
mobile | string | Required. Customer mobile, 10 digits. |
senderName | string | Required. Customer name. |
category | string | Required. The bill category. |
district, state | string | Optional. Some electricity boards need them. |
A bill lookup is free. Nothing is debited until you call Pay a bill.
Test in the sandbox
| You want | Send |
|---|---|
| A bill | Any account number |
| No bill found | An account number ending in 0000 |
Pay a bill
Pay a bill from your wallet.
Request body
| Field | Type | Notes |
|---|---|---|
referenceId | string | Required. Your unique id, 6 to 60 letters, numbers, - or _. It is the idempotency key. |
operatorCode | string | Required. |
account | string | Required. |
mobile | string | Required. Customer mobile. |
senderName | string | Required. |
category | string | Required. |
amount | number | Required. Rupees, 1 to 2,00,000, up to 2 decimals. Use the amount from the bill. |
Response
| Field | Meaning |
|---|---|
referenceId | Your reference, returned unchanged |
type | The kind of transaction |
amount, fee, totalDebit | Rupees. totalDebit is amount plus fee. |
status | success, failed or pending |
utr | Reference number from the bank or biller, when available |
reason | Why it failed, empty otherwise |
environment | sandbox or production |
Debit and refund. A live call debits amount + fee from your wallet first. If it fails, the full debit returns to your wallet. If the bank has not answered, the status is pending and the debit stays. Call the status endpoint to follow it up. Never retry with a new reference.
Idempotency. A referenceId that already exists returns the original result with "duplicate": true. Nothing new is debited.
Test in the sandbox
| You want | Send |
|---|---|
| Success | Any account number |
| Failure | An account number ending in 0000 |
| Duplicate | The same referenceId twice |
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Bill payment status
Fetch a bill payment by your own reference.
Use this when a request timed out, or when the status was pending. If you are unsure whether a pay call reached BankMe, call this first. Do not pay again with a new reference.
Check a sender
Check whether a customer (the sender) is already registered for money transfer.
Send money in five steps. Check the sender, register them if needed, add the recipient, request the OTP, then transfer. Each step has its own call below.
Limits and fees
| Rule | Value |
|---|---|
| Minimum per transfer | Rs 100 |
| Maximum per transfer | Rs 49,999 |
| Per customer per day | Rs 2,00,000 |
| Sender fee, up to Rs 25,000 | 1% including GST, minimum Rs 10 |
| Sender fee, Rs 25,001 to Rs 49,999 | Rs 295 (Rs 250 plus 18% GST) |
| Customer confirmation | Every transfer is confirmed with the OTP sent to the customer's mobile |
Request body
| Field | Type | Notes |
|---|---|---|
mobile | string | Required. The sender's 10 digit mobile. |
Test in the sandbox
| You want | Send |
|---|---|
| A registered sender | Any mobile number |
| A sender not yet registered | A mobile number ending in 0000 |
Sandbox first. Use your sandbox key on https://sandbox.bankme.co.in. Results are simulated, your wallet is never debited and no real money moves.
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Register a sender
Register a new sender before their first transfer.
Request body
| Field | Type | Notes |
|---|---|---|
mobile | string | Required. |
name | string | Required. As on the sender's ID. |
dob | string | Required. YYYY-MM-DD. |
line | string | Required. Address line. |
city | string | Required. |
state | string | Required. |
pincode | string | Required. |
district, area | string | Optional. |
List recipients
List the recipients already saved for a sender.
Query
| Field | Type | Notes |
|---|---|---|
mobile | string | Required. The sender's mobile. |
Add a recipient
Save a bank account as a recipient for a sender.
Request body
| Field | Type | Notes |
|---|---|---|
mobile | string | Required. The sender's mobile. |
recipientName | string | Required. Account holder name. |
account | string | Required. 6 to 20 digits. |
ifsc | string | Required. Valid IFSC code. |
recipientMobile | string | Optional. Defaults to the sender's mobile. |
Check the account first. Call Verify a document with bank_pennyless or bank_pennydrop before you save the recipient. It confirms the account holder's name and avoids failed transfers.
Send the transfer OTP
Send the confirmation OTP to the sender's mobile for one transfer.
Request body
| Field | Type | Notes |
|---|---|---|
mobile | string | Required. The sender's mobile. |
recipientId | integer | Required. From the recipient calls. |
amount | number | Required. Rs 100 to Rs 49,999. |
Response
| Field | Meaning |
|---|---|
otp.otpRefId | Send this with the transfer, together with the OTP the customer received |
The call is refused with LIMIT_PER_DAY if the amount is more than the customer can still send today.
Transfer money
Send money to the recipient's bank account.
Request body
| Field | Type | Notes |
|---|---|---|
referenceId | string | Required. Your unique id, 6 to 60 letters, numbers, - or _. It is the idempotency key. |
mobile | string | Required. The sender's mobile. |
recipientId | integer | Required. |
amount | number | Required. Rs 100 to Rs 49,999. |
otp | string | Required. The OTP the sender received. |
otpRefId | string | Required. From the OTP call. |
state | string | Optional. State code. A default is used if you leave it out. |
Response
| Field | Meaning |
|---|---|
referenceId | Your reference, returned unchanged |
type | The kind of transaction |
amount, fee, totalDebit | Rupees. totalDebit is amount plus fee. |
status | success, failed or pending |
utr | Reference number from the bank or biller, when available |
reason | Why it failed, empty otherwise |
environment | sandbox or production |
Debit and refund. A live call debits amount + fee from your wallet first. If it fails, the full debit returns to your wallet. If the bank has not answered, the status is pending and the debit stays. Call the status endpoint to follow it up. Never retry with a new reference.
Commission, where your agreement includes it, is credited back to your wallet when a transfer succeeds.
Test in the sandbox
| You want | Send |
|---|---|
| Success | OTP 123456 |
| Failure | OTP 000000. The result is failed with the reason "Sandbox: simulated failure". |
| Wrong OTP | Any other OTP. The call is refused with INVALID_OTP. |
| Above the limit | An amount over 49,999. The call is refused with LIMIT_PER_TXN. |
| Duplicate | The same referenceId twice |
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Transfer status
Fetch a transfer by your own reference.
Use this when a request timed out, or when the status was pending. Never create a new transfer with a new reference to retry.
Balance enquiry
Check a customer's bank balance with their Aadhaar and fingerprint.
AEPS needs a registered merchant. BankMe sets up your merchant account when it activates AEPS for you. Calls come from a certified fingerprint device at the customer's side.
Request body
| Field | Type | Notes |
|---|---|---|
customerId | string | Required. The customer's 10 digit mobile. |
aadhaar | string | Required. 12 digit Aadhaar number. |
latlong | string | Required. The device location, for example 11.0168,76.9558. |
bankCode | string | Required. The customer's bank code. |
piddata | string | Required for balance, statement and withdrawal. The biometric capture from the fingerprint device. Never store it. |
Test in the sandbox
| You want | Send |
|---|---|
| Success | Any 12 digit Aadhaar number |
| Failure | An Aadhaar number ending in 0000. The response says the biometric did not match. |
Sandbox first. Use your sandbox key on https://sandbox.bankme.co.in. Results are simulated, your wallet is never debited and no real money moves. Use any text for piddata in the sandbox.
Going live. BankMe activates each API for production after your integration is approved. Until then, live calls are refused with NOT_ACTIVATED or NOT_AVAILABLE.
Mini statement
Get the last few transactions of the customer's account.
Request body
Same fields as Balance enquiry.Withdrawal OTP
Request the OTP for a cash withdrawal above Rs 5,000.
Request body
| Field | Type | Notes |
|---|---|---|
customerId | string | Required. The customer's 10 digit mobile. |
aadhaar | string | Required. 12 digit Aadhaar number. |
latlong | string | Required. The device location, for example 11.0168,76.9558. |
bankCode | string | Required. The customer's bank code. |
amount | integer | Required. Whole rupees, above 5000. |
Withdrawals of Rs 5,000 or less do not need an OTP. Use the withdrawal call directly.
Cash withdrawal
Pay cash to the customer against their Aadhaar and fingerprint.
Request body
| Field | Type | Notes |
|---|---|---|
customerId | string | Required. The customer's 10 digit mobile. |
aadhaar | string | Required. 12 digit Aadhaar number. |
latlong | string | Required. The device location, for example 11.0168,76.9558. |
bankCode | string | Required. The customer's bank code. |
piddata | string | Required for balance, statement and withdrawal. The biometric capture from the fingerprint device. Never store it. |
amount | integer | Required. Whole rupees. |
txnOtpRequestId | string | Required when the amount is above 5000. From the withdrawal OTP call. |
Your commission on a successful withdrawal is credited to your wallet as per your agreement.
Test in the sandbox
| You want | Send |
|---|---|
| Success | Any valid Aadhaar number |
| Failure | An Aadhaar number ending in 0000 |
| Above Rs 5,000 | Send txnOtpRequestId from the withdrawal OTP call |
Errors for the other APIs
Verification, BBPS, DMT and AEPS answer errors in the same shape as the Payout API: success false, a code and a message.
| Code | HTTP | Meaning |
|---|---|---|
INVALID_INPUT | 400 | A field is missing or in the wrong format. The message names it. |
INVALID_REFERENCE | 400 | referenceId must be 6 to 60 letters, numbers, - or _. |
INVALID_AMOUNT | 400 | The amount is outside the allowed range. |
LIMIT_PER_TXN | 400 | A DMT transfer is above Rs 49,999. |
LIMIT_PER_DAY | 422 | The customer has used the Rs 2,00,000 daily DMT limit. |
INVALID_OTP | 422 | Sandbox only. Use 123456 or 000000. |
INSUFFICIENT_BALANCE | 402 | Your wallet is too low. Nothing was debited. |
NOT_ACTIVATED | 403 | This API is not activated for production on your account. |
PARTNER_INACTIVE | 403 | Your partner account is not active. |
NOT_AVAILABLE | 503 | Live access is not switched on yet. Use the sandbox. |
UNKNOWN_PRODUCT | 404 | The verification product id does not exist. |
NOT_FOUND | 404 | No transaction with this referenceId. |
IN_PROGRESS | 409 | The same referenceId is still being processed. Wait, then check the status. |
REQUEST_FAILED | 422 | The provider could not complete the request. Check your input. |
PROVIDER_ERROR | 502 | The bank or provider is not available. You were not charged. Try again. |
SERVER_ERROR | 500 | Something went wrong on our side. You were not charged. |
A request that fails validation never debits your wallet. For a timeout, check the status endpoint before you try again.
Webhooks
BankMe posts the result of each payout to the HTTPS URL registered for your key. Use it instead of polling.
Events
| Event | Sent when |
|---|---|
payout.success | A payout was paid |
payout.failed | A payout failed and was refunded |
webhook.test | A test delivery was requested |
Headers on every delivery
| Header | Value |
|---|---|
x-bankme-event | The event name |
x-bankme-timestamp | Unix time of the delivery |
x-bankme-signature | sha256= followed by the hex HMAC-SHA256 of timestamp + "." + rawBody, keyed with your signing secret (starts with whsec_) |
Delivery rules
- Reply with any
2xxstatus within a few seconds to acknowledge. Anything else counts as a failure. - Failed deliveries are retried automatically, so you can receive the same event more than once. Make your handler idempotent by looking up the payout by
referenceId. - The webhook URL must be
httpsand use a public domain name. IP addresses, localhost and private networks are rejected. - If your firewall limits inbound traffic, allow BankMe's outbound ranges:
74.220.52.0/24and74.220.60.0/24. - A lost webhook never loses the result. Fetch it any time with Payout status.
Verify a webhook
Always check the signature before you trust a delivery.
- Read the raw request body as received. Do not parse and re-encode it first.
- Build the string
timestamp + "." + rawBody. - Compute HMAC-SHA256 of it with your
whsec_secret and compare withx-bankme-signaturein constant time. - Reject deliveries whose timestamp is more than 5 minutes old.
- Process the event, then reply
200.
Store your whsec_ secret in server-side configuration, never in code. If it leaks, ask BankMe to rotate it.
Send a test webhook
Sends a signed webhook.test delivery to your registered URL so you can check your handler end to end.
Works in the sandbox and in production. Your endpoint should verify the signature and reply 200 exactly as for a real event.
Sandbox testing
The sandbox behaves like production but never moves money and never debits a wallet.
| You want | Send |
|---|---|
| Success | Any valid beneficiary, for example account 123456789012 |
| Failure | A bank account number ending in 0000, or a UPI ID starting with fail. The result is failed with the reason "Sandbox: simulated failure". |
| Duplicate | Send the same referenceId twice. The second response has "duplicate": true. |
Sandbox payouts carry "environment": "sandbox" and a UTR starting with SBX. Webhooks are delivered from the sandbox too and signed the same way.
Go-live checklist
- Your integration passes the sandbox tests: success, failure, duplicate and webhook handling.
- Your API secret and webhook signing secret are in server-side configuration.
- Every payout uses a new, unique
referenceId. You reuse one only to retry the same payout. - You called GET /v1/ip from your production server and gave that IP address to BankMe.
- Your webhook endpoint verifies the signature and replies with
2xx. - BankMe issues your production key. Change the base URL to
https://api.bankme.co.inand use the production key and secret. - Make a first small live payout (Rs 100) and confirm the UTR before going to volume.
Payouts are paid from your own BankMe wallet. Keep it funded before you send live payouts.