Penny drop and name match are not two competing ways to verify a bank account. They are two halves of one check. A penny drop is how you find out whether an account exists, accepts a credit and who the bank says holds it. A name match is how you decide whether that holder is the person or business you meant to pay. The StampMitra bank account endpoint does both in a single call: it returns the account status and the name at the bank, and, when you send a name, a nameMatchResult and nameMatchScore comparing the two.
The reason the distinction matters is that each half fails differently. A penny drop can succeed on an account that belongs to someone else. A name match can be perfect on an account that is closed. A payout flow that relies on only one will either pay the wrong person or reject a good one. This guide explains what each half proves, where the name at the bank comes from and why it differs from the name on a PAN card, how to turn the response into accept, review and reject decisions, how it compares with lighter and heavier alternatives, and the one place where retries need care.
Key takeaways
- A penny drop confirms the account exists and is reachable and returns the holder name as the bank holds it. A name match compares that name with the one you expect. You need both.
- One endpoint,
POST /v1/verify/bank-account, scopeverification.bank-account.read. RequiredaccountNumber(9 to 18 digits) andifsc(11 characters). Optionalnameandphone. - The response carries
valid,nameAtBank,bankName,utr,city,branch,micr,nameMatchScore,nameMatchResult,accountStatus,accountStatusCode,ifscDetailsand areferenceId. - In the documented sample
nameMatchScoreis a string ("100"),nameMatchResultisGOOD_MATCHandaccountStatusisACCOUNT_IS_VALID. The reference shows one example of each, so treat the set of values as open. - The name at the bank is often shorter, abbreviated or in a different order than a person's formal name. Build for that: normalise, score, then review.
- Compare the bank name with the payee you verified elsewhere (for example the name on their PAN), not only with what they typed.
- The reference does not say that verification calls are idempotent. A timeout on a penny drop is not safe to retry blindly: check whether the first call completed.
- Bank account verification is flagged higher risk in StampMitra's production access form and opens after an extra check, so apply early.
- A valid account with a matching name proves the account is real and plausibly theirs. It does not prove their intent or that the account is not a pass-through.
Two ideas that people merge
When someone says "we do penny drop verification", they usually mean four separate things at once.
| Question | What answers it | Failure it catches |
|---|---|---|
| Does this account exist and is it open? | The penny drop itself, a small credit that succeeds or fails | Typos in the number or IFSC, closed or frozen accounts |
| Who does the bank say owns it? | The beneficiary name returned with the credit | Paying a different person than intended |
| Does that name match who I expect? | A name comparison, which is your step or the platform's | Wrong payee, mule or borrowed accounts, careless typos |
| Does the person asking own this account? | Not answered by either | Impersonation, account takeover |
The first two come from the bank through the transfer rail. The third is a comparison, which can be done by the API (nameMatchScore) and again by you. The fourth is not answered by a penny drop at all, and needs another factor, such as a document, a reverse penny drop or a payment from the customer's own account.
Keeping these four questions separate makes the design obvious. You can fail on any of the first three automatically, and you route only the third to a human when the score is borderline.
How a penny drop works, and what you learn
A penny drop sends a very small credit to the account being checked and reads the outcome. In bank transfer terms it is an ordinary credit to a beneficiary, using the account number and IFSC you supply. If the credit succeeds, the account exists, is open to credits and is correctly addressed. The bank also returns the beneficiary name as it holds it, and the transfer carries a reference, which in the StampMitra response is the utr field.
Four caveats shape how you use it.
First, the amount and who bears its cost are not stated in the StampMitra reference. The product description calls the endpoint a penny-drop confirmation of an account and holder name, and the response includes a utr. Do not tell your customers an amount until you have confirmed it with support. Do tell account holders that a very small credit may appear in their statement, so that nobody is surprised.
Second, accounts can be valid and still refuse credits for reasons that have nothing to do with fraud: a frozen or dormant account, a restriction on the account type, or a temporary bank-side failure. A failed credit is therefore evidence about the account, not proof of dishonesty. Handle it with "please check the details or try another account", not an accusation.
Third, the name the bank returns is whatever the bank has on its books. It follows the bank's own rules for length, abbreviation and order. It can differ from the PAN name and the name on a cheque book.
Fourth, the check depends on the bank's systems being reachable. That brings failure modes (timeouts, upstream unavailable) that a purely format-based check does not have, and it is why retry behaviour deserves its own section below.
The StampMitra bank account endpoint
| Item | Value |
|---|---|
| Path | POST /v1/verify/bank-account on https://api-b2b.stampmitra.in |
| Auth | Authorization: Bearer <key> or x-api-key: <key> |
| Scope | verification.bank-account.read |
| Required fields | accountNumber (9 to 18 digits), ifsc (11 characters, for example ICIC0001234) |
| Optional fields | name (beneficiary name to match), phone (account holder's 10-digit Indian mobile number, when available; improves match confidence) |
| Billing | Metered as bank-account.verify after a successful response; bank account verification has its own rate on the pricing page |
| Sandbox | A key starting sk_test_; sample data only; never debits your wallet |
curl -X POST "https://api-b2b.stampmitra.in/v1/verify/bank-account" \
-H "Authorization: Bearer $STAMPMITRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accountNumber":"000123456789","ifsc":"ICIC0001234","name":"Ravi Kumar"}'{
"referenceId": "SMV-20260927-3B71A0",
"valid": true,
"nameAtBank": "RAVI KUMAR",
"bankName": "ICICI Bank",
"utr": "017952100012345",
"city": "Mumbai",
"branch": "Andheri West",
"micr": "400229002",
"nameMatchScore": "100",
"nameMatchResult": "GOOD_MATCH",
"accountStatus": "ACCOUNT_IS_VALID",
"accountStatusCode": "1",
"ifscDetails": {
"bank": "ICICI Bank",
"ifsc": "ICIC0001234",
"micr": "400229002",
"address": "Andheri West, Mumbai",
"city": "Mumbai",
"state": "Maharashtra",
"branch": "Andheri West"
}
}| Field | Meaning | Use |
|---|---|---|
referenceId | StampMitra's id for this call, form SMV-YYYYMMDD-XXXXXXX | Store it. It is not an idempotency key |
valid | Whether the account verified | Branch on it first |
nameAtBank | Holder name as the bank returned it | The string to compare against the payee |
bankName, branch, city, micr | Details of the branch | Show the bank and branch back to the user so they can spot a wrong IFSC |
utr | Reference of the bank transfer used for the check | Keep it with the record; it is how the bank can trace the call |
nameMatchScore | Comparison score between your name and the bank's name, returned as a string ("100" in the sample) | Parse to a number before comparing |
nameMatchResult | Label of the comparison (GOOD_MATCH in the sample) | A label; do not hard-code the set of values |
accountStatus, accountStatusCode | Status of the account (ACCOUNT_IS_VALID and "1" in the sample) | Decide on status before name |
ifscDetails | Branch details from the IFSC | Context; check state or bank against what the user claimed |
Compare this with the PAN endpoint, which also returns a nameMatchScore string and a nameMatchResult, but with different label values. A shared helper that handles both must treat labels as open sets and parse the score for each.
Reading accountStatus, nameMatchResult and nameMatchScore
The reference documents one example for each of these fields. It does not enumerate other statuses or labels, and it does not publish the comparison logic or score thresholds. Design around that.
For status, write "ACCOUNT_IS_VALID passes; anything else is not a pass". An unrecognised status goes to review or to "please try another account", depending on your product, and you log the raw value and referenceId. For the name, treat nameMatchResult as a hint and nameMatchScore as a signal, and apply your own threshold, calibrated on your own data, using the method in how to verify a PAN and match the name. Everything said there about thresholds, normalisation, layering and review applies here too.
The one difference to note is the target. For PAN, the target name is the person's registered name. For a bank account the target is the bank's own string, which is typically shorter and more abbreviated than the formal name. A threshold calibrated on PAN names will often be too strict for bank names.
Why the name at the bank differs from the name you expect
Bank names are the single largest source of false rejections in payout flows. They are not wrong. They are shaped by the system that stores them.
| Pattern | Example | Cause |
|---|---|---|
| Truncation | "RAVI KUMAR SHARMA ENTERPR" | Beneficiary name fields have a character limit on some rails and banks |
| Surname dropped or reduced to an initial | "RAVI K" | The bank holds an abbreviated name |
| Different order | "KUMAR RAVI" | The bank stores surname first |
| Titles | "MR RAVI KUMAR", "SMT PRIYA SHARMA" | Honorifics held in the name field |
| Joint accounts | First holder only | Only the primary holder's name is returned |
| Business accounts | Trade name rather than legal name | The account was opened under the brand |
| Proprietorship | Proprietor's name or shop name | Either can be on the account |
| Minor, HUF or other special accounts | "RAVI KUMAR HUF" or a guardian's name | The account is in a different capacity |
| Spelling variants | "LAXMI" and "LAKSHMI" | Names were entered by hand at account opening |
| Legacy data | Old name after a change | The bank did not update its record |
Two lines of defence follow. First, normalise both strings (case, punctuation, honorifics, abbreviations such as "MOHD" and "MOHAMMED") and compare tokens, as in the PAN guide. Second, decide in advance how much of the difference your product tolerates for each kind of payee. A gig worker paid small amounts weekly can tolerate more variance than a lender disbursing a loan.
Which name should you compare against?
"Compare to the payee name" is not specific enough. You usually have three candidate names, and which you pick changes the result.
| Candidate | Where it comes from | When to use |
|---|---|---|
| Name the payee typed in your form | The user | Weakest. It is whatever the user chose to write |
| Registered name from PAN verification | registeredName in the PAN response | Strongest for individuals: an independent record, not typed by the payee |
| Legal name from GSTIN or CIN lookup | legalName or companyName | Strongest for businesses |
For individuals, the better design is to verify the PAN first, then compare the bank's nameAtBank with the PAN's registeredName, and use the typed name only as a fallback. A mismatch then means the account is probably not in the name of the person whose PAN you verified, which is exactly the signal you want. For businesses, compare with the registered legal name and be prepared to accept the trade name for proprietorships, where the account may be in either. The wider picture of which identifier to verify first is in PAN vs GSTIN vs CIN.
In your request, still send name. The platform's comparison is part of the same call and costs nothing extra. Send the name you will use as the target, so the score you receive is the score you act on.
Turning the response into a decision
Keep the decision in one pure function. The version below accepts a clean match, sends partial matches and unknown statuses to review, and fails only when the lookup says the account is not valid.
// bank-decision.mjs
const num = (v) => Number.parseFloat(String(v ?? "").replace(/[^0-9.]/g, ""));
export function decideBank(res, { acceptScore = 85, rejectScore = 40 } = {}) {
const ref = res.referenceId;
if (!res.valid) return { decision: "FAIL", reasons: ["ACCOUNT_NOT_VALID"], ref };
const reasons = [];
if (res.accountStatus !== "ACCOUNT_IS_VALID") reasons.push(`STATUS_${String(res.accountStatus || "EMPTY").toUpperCase()}`);
const score = num(res.nameMatchScore);
if (Number.isNaN(score)) reasons.push("NO_SCORE");
else if (score < rejectScore) reasons.push("NAME_FAR");
else if (score < acceptScore) reasons.push("NAME_PARTIAL");
if (reasons.includes("NAME_FAR")) return { decision: "FAIL", reasons, ref };
if (reasons.length) return { decision: "REVIEW", reasons, ref };
return { decision: "PASS", reasons, ref };
}The two thresholds are placeholders to show the shape, not recommendations. Calibrate them on your own results. The structure is what to copy: a high threshold above which you pass, a low threshold below which you fail, and a review band between them. A very low score on a valid account is a signal that the account may belong to someone else, which is why it fails rather than going to review.
And the same logic in Python:
import re
def decide_bank(res: dict, accept_score: float = 85.0, reject_score: float = 40.0) -> dict:
ref = res.get("referenceId")
if not res.get("valid"):
return {"decision": "FAIL", "reasons": ["ACCOUNT_NOT_VALID"], "ref": ref}
reasons = []
if res.get("accountStatus") != "ACCOUNT_IS_VALID":
reasons.append("STATUS_" + str(res.get("accountStatus") or "EMPTY").upper())
try:
score = float(re.sub(r"[^0-9.]", "", str(res.get("nameMatchScore") or "")))
except ValueError:
score = None
if score is None:
reasons.append("NO_SCORE")
elif score < reject_score:
reasons.append("NAME_FAR")
elif score < accept_score:
reasons.append("NAME_PARTIAL")
if "NAME_FAR" in reasons:
return {"decision": "FAIL", "reasons": reasons, "ref": ref}
return {"decision": "REVIEW" if reasons else "PASS", "reasons": reasons, "ref": ref}Validate the inputs before spending a call
A malformed value fails with a 400 before anything is sent to a bank. The documented message is accountNumber must be a 9-18 digit numeric bank account number. Catch it earlier.
// bank-input.mjs
export function checkBankInput({ accountNumber, ifsc }) {
const acct = String(accountNumber ?? "").replace(/\s+/g, "");
const code = String(ifsc ?? "").replace(/\s+/g, "").toUpperCase();
if (!/^[0-9]{9,18}$/.test(acct)) return { ok: false, field: "accountNumber", reason: "Account numbers here are 9 to 18 digits." };
if (!/^[A-Z]{4}0[A-Z0-9]{6}$/.test(code)) return { ok: false, field: "ifsc", reason: "An IFSC has 11 characters: four letters, a zero, then six letters or digits." };
return { ok: true, accountNumber: acct, ifsc: code };
}The IFSC shape (four letters, a zero, six alphanumeric characters) is the standard format. A pattern check cannot tell whether the branch exists, but it removes typing errors for free. Ask the user to type the account number twice if your flow can afford the friction, since a one-digit mistake in an account number can still pass the length check and, with a valid IFSC, point at a different real account.
Retries and duplicate calls: the one place to be careful
For PAN, GSTIN and CIN, retrying a timed-out call is usually harmless beyond the cost. For a penny drop, a call involves an actual transfer through a bank. A timeout on your side does not tell you whether the transfer went through. A blind retry can mean two credits to the same account and two charges to you.
The reference says the verification endpoints are not idempotent: the referenceId identifies a call and is explicitly not an idempotency key, and no idempotency header is documented for them. So protect yourself.
- Make your job idempotent. Record "verification requested" against the payee and the account before you call, and record the outcome after. If a job restarts, it must check the record before issuing a second call.
- Do not retry timeouts automatically. Retry 429 (the request was rejected for rate, nothing was sent) with backoff. For a timeout or a 502 after the request was sent, first look for evidence that the first call completed.
- Check the logs.
GET /v1/logswithservice=bank-account.verify, afromtime just before your call and astatusfilter shows whether the platform recorded a call. If it did, use that result rather than calling again. - Surface the ambiguity. If you cannot tell, tell the user "we are still checking this account" and resolve it asynchronously, rather than trying again in a tight loop.
The wider rules for each status code are in errors, retries and idempotency. The point to carry from there is that bank account verification is the endpoint where "just retry it" is least safe.
Alternatives, and where each fits
A penny drop is one of several ways to establish that an account is real and plausibly the payee's. They differ in what they prove, the friction for the user, the cost and what they require.
| Method | What it proves | Friction | Notes |
|---|---|---|---|
| Penny drop (this endpoint) | Account exists, accepts credits, and the bank's holder name | Low: the user types account number and IFSC | The user may see a small credit in their statement |
| Reverse penny drop | That someone with control of the account sent you money | Medium: the user must make a payment | Stronger on control of the account; needs a collection setup that StampMitra does not provide through this endpoint |
| UPI handle verification | That a handle exists and returns a name | Low | Proves a UPI address, which may not be the settlement account you want |
| Cancelled cheque or bank statement | That the payee can produce a document | Medium | Needs review, and documents can be altered |
| Consent-based account data sharing | Account details shared by the customer through a regulated channel | Medium | A separate regulated flow, not part of this endpoint |
For most payout and vendor flows, a penny drop with a name match is the proportionate default. Use a stronger method when the risk is takeover or impersonation, and a document when your policy or your partner bank asks for one. These methods are complementary. A cancelled cheque image plus a matching penny drop tells you more than either alone.
Three payees, three policies
The same response deserves different treatment depending on what you are about to pay and what an error costs. Here are three fictional situations that use the same decision shape with different thresholds.
A weekly payout to a gig worker. Amounts are small and frequent, there are thousands of payees, and a wrongly rejected worker is a support ticket and a churned partner. The policy: verify the PAN first, compare the bank's nameAtBank with the PAN's registered name using a generous threshold, and let a partial match pass with a flag if the first name and the surname initial agree. Route the rest to a review lane that asks for a cancelled cheque photograph. Hold the first payout until the check resolves, then release the held amount. The cost of a mistake is bounded by one week of earnings.
A vendor onboarding for a trading company. Amounts are larger, vendors are fewer and each is worth real effort. The policy: verify the vendor's PAN, GSTIN and, for a company, CIN; then verify the bank account and compare against the legal name from the lookup, accepting the trade name only for proprietorships. A partial match always goes to a person, who asks for a cancelled cheque or a bank letter. A changed account later triggers the same flow and a second approver before the first payment. The cost of a mistake here is a misdirected invoice payment, which is why the review lane is not optional.
A loan disbursement. The amount is large, the borrower has been through full KYC, and the account will receive money that is hard to recall. The policy is strict: the bank name must match the verified borrower name at a high threshold, joint accounts are reviewed, and any partial match goes to a person with the borrower's documents in front of them. The lender also checks the ifscDetails against the borrower's stated bank and city. A name mismatch blocks disbursement until resolved, however embarrassing the delay. Regulated lenders should also confirm with their regulator or partner bank what disbursement controls are required, since those rules sit outside this API.
The three policies use the same decideBank function with different acceptScore and rejectScore values, and differ mainly in what the review lane asks for and what holds money. The code stays the same. Policy is configuration.
Designing the account entry screen
Most failed verifications are born in the form. A few details reduce them.
Ask for the account number twice, or show it back in groups. A single wrong digit in a nine to eighteen digit number can produce a different, real account. Asking twice, or displaying the number in groups of four for the user to check, costs a second and prevents the most expensive typo.
Collect the IFSC from a selector where you can. A bank-and-branch picker that fills the IFSC avoids transposed characters. If users must type it, validate the pattern as shown above and, after a successful call, show back the bank name and branch from the response ("State Bank of ..., Andheri West") so the user can notice a wrong IFSC.
Say what will happen. One line beneath the button: "We will send a very small amount to this account to confirm it. You do not need to do anything." That sentence prevents a flood of support messages from users who spot an unexpected credit. Check the actual behaviour with support before you publish the line.
Give failure messages a next action. Compare these.
| Situation | Poor message | Better message |
|---|---|---|
valid false | "Verification failed" | "We could not confirm this account. Please check the account number and IFSC, or try another account." |
| Name far from the payee | "Name mismatch" | "The name on this account looks different from the name we have for you. Please use an account in your own name, or upload a cancelled cheque." |
| Partial match | "Match score low" | "We are checking a small difference in the name. This usually takes less than a day." |
| Timeout | "Error" | "The bank did not respond. We are checking whether the verification went through and will update you." |
The last row connects to the retry rules above: the message tells the truth about the ambiguity and does not invite the user to resubmit.
Never reveal the bank's returned name to someone who has not proven they own the payee profile. Showing "We found: RAVI KUMAR SHARMA" in response to a stranger's guess turns your form into a name lookup service.
Evidence and disputes
Sooner or later a payee will say you paid the wrong account, or a partner bank will ask how you decided to pay an account. The verification record is your answer, and it only helps if you kept the right pieces.
For each verification, keep: the payee id, the referenceId, the utr, the time of the call, the account number masked to its last four digits and the full IFSC, the nameAtBank or a hash of it, the score, the status, your decision and its reasons, and the identity of any reviewer who overrode the default. With the referenceId and the utr, StampMitra support and the bank can trace the specific call. With the reasons, you can show why a partial match was accepted, and who accepted it.
When a dispute arrives, resist the urge to re-run the verification as proof. A new call reflects the bank's data today, not the data at the time of your decision, and it adds a second referenceId that muddies the record. Pull the original record, read its fields, and only re-verify if you need the current state to decide a next step. If the underlying account details were wrong at the time, your record should show that the check passed and why, which is what lets you work out whether the failure was a policy gap, a data problem or fraud.
Where verification sits in a payout flow
Place it at the moment an account becomes a payment destination, and again whenever that changes.
- At onboarding. Verify the payee's identity first (PAN for individuals, GSTIN and CIN for businesses), then verify the bank account and compare the bank's name with the verified name.
- On change of account. A changed bank account is one of the classic fraud events in vendor and payout systems. Treat it like a new payee: verify again, and consider a hold or a second approval before the first payment to the new account.
- On a schedule, if your policy needs it. Accounts close and freeze. A periodic re-check of dormant payees before a large payment catches accounts that are no longer valid.
- Before large one-off payments. The cost of a call is small compared with a misdirected high-value payment.
Store the referenceId, the utr, the decision, the reasons, the score and the time against the payee. The guide on what to log and keep describes what to retain and what to mask. The bank account number is sensitive: show only the last four digits in your own interfaces.
What this check does and does not protect against
| Risk | Does the check help? |
|---|---|
| Typo in account number or IFSC | Yes. The credit fails or goes to a name that does not match |
| Paying the wrong person by mistake | Yes, if you compare to a verified name |
| Payee supplies someone else's valid account | Partly. A name mismatch shows it, but a matching name on a borrowed account of a relative does not |
| Mule or pass-through accounts | No. A mule account can be genuinely valid and in a real person's name |
| Account takeover of a legitimate payee | No. The details are valid; the person changing them is not the payee |
| Closed or frozen account | Yes, in most cases the credit fails |
| Intent to defraud | No |
Say this plainly to your risk team. Verification narrows a class of errors and some frauds. It does not make a payee trustworthy.
Testing and going live
In sandbox, the documented test values are account SANDBOX0000000001 for a valid account and SANDBOX9999999999 for not valid. Any other input gets a stable but arbitrary outcome, roughly two in three valid, and every name is synthetic. Note that those test values do not follow the production format of nine to eighteen digits. If a request with them is rejected by validation on your account, treat that as a mismatch between the documented test values and the format check, and ask support instead of coding around it. Sandbox never contacts a bank, so it cannot show you how real bank names differ from real PAN names. Test your decision function with fixtures built from the patterns in the table above, and calibrate thresholds with real data after go-live.
Bank account verification is one of the products the production access form flags as higher risk, so it opens after a short extra check once your application is approved. Plan the timing: apply for it with the rest of your products, not after launch. The full path, including documents, provisional limits and the wallet, is in sandbox vs production: what you can test.
Cost and ordering
Bank account verification has its own rate: ₹4 per call on pay-as-you-go, with plan pricing in the pricing table. Because each call involves a bank transfer, a failed or retried call is a cost you want to avoid, which makes ordering matter. Run offline input checks first. Verify the payee's identity next. Call the bank endpoint last, and only for payees who passed the earlier steps. The order of checks across a whole vendor flow is covered in Building a vendor onboarding flow.
Common mistakes
- Treating penny drop and name match as alternatives instead of two halves of one check.
- Comparing the bank name only with what the user typed instead of an independently verified name.
- Using a PAN-calibrated threshold for bank names, which are shorter and more abbreviated.
- Retrying a timed-out call blindly and sending two credits.
- Rejecting joint-account or trade-name cases outright instead of routing them to review.
- Hard-coding the set of
accountStatusornameMatchResultvalues from a single sample. - Skipping re-verification when a payee changes their bank account.
- Showing full account numbers in logs and screens.
- Telling customers an amount without confirming it.
- Assuming a valid account with a matching name means a trustworthy payee.
What to do next
Get a sandbox key through the quickstart, call the endpoint with the sandbox values, and read the bank account reference and the product page. Then read how to verify a PAN and match the name for the matching method this guide builds on. For the whole payee flow, see KYC vs KYB and the vendor flow guide. Questions go to support.
API reference at a glance
Bank Account
POST /v1/verify/bank-accountRequest parameters
| Field | Type | Description |
|---|---|---|
Authorizationrequired | header | Bearer <api-key> (e.g. sk_test_... in sandbox, sk_live_... in production). The x-api-key header is also accepted. |
accountNumberrequired | string | 9-18 digit numeric bank account number. |
ifscrequired | string | 11-character IFSC code, e.g. ICIC0001234. |
name | string | Beneficiary name to match. |
phone | string | Account holder's 10-digit Indian mobile number, when available; improves match confidence. |
POST /v1/verify/bank-account{
"referenceId": "SMV-20260927-3B71A0",
"valid": true,
"nameAtBank": "RAVI KUMAR",
"bankName": "ICICI Bank",
"utr": "017952100012345",
"city": "Mumbai",
"branch": "Andheri West",
"micr": "400229002",
"nameMatchScore": "100",
"nameMatchResult": "GOOD_MATCH",
"accountStatus": "ACCOUNT_IS_VALID",
"accountStatusCode": "1",
"ifscDetails": {
"bank": "ICICI Bank",
"ifsc": "ICIC0001234",
"micr": "400229002",
"address": "Andheri West, Mumbai",
"city": "Mumbai",
"state": "Maharashtra",
"branch": "Andheri West"
}
}POST /v1/verify/bank-accountBank Account: ₹4 per successful call, pay-as-you-go (lower on Growth and Scale).
Frequently asked questions
What is the difference between penny drop and name match?
A penny drop sends a small credit to learn whether the account exists, accepts credits and who the bank says holds it. A name match compares that holder name with the person or business you expect. They are two halves of one check, not alternatives.
What does the StampMitra bank account endpoint return?
It returns valid, nameAtBank, bankName, utr, city, branch, micr, nameMatchScore, nameMatchResult, accountStatus, accountStatusCode, ifscDetails and a referenceId. Send name to get a match comparing your name with the bank's, and optionally phone to improve match confidence.
Why does the name at the bank differ from the PAN name?
Banks store names under their own rules: truncation, abbreviation, surname first, honorifics, only the first holder of a joint account, trade names on business accounts and legacy names after a change. Normalise both strings, score the match and route partial matches to review.
Which name should I compare the bank name against?
Prefer an independently verified name over what the payee typed: the registered name from the PAN lookup for individuals, and the legal name from GSTIN or CIN for businesses. A mismatch then signals that the account may not belong to the person you verified.
Is it safe to retry a timed-out bank account verification?
Not blindly. The call involves a bank transfer and the reference documents no idempotency for verification endpoints, so a retry can send a second credit. Record each attempt, check the request logs for a completed call first, and retry only 429 responses automatically.
Is nameMatchScore a number?
In the documented sample it is a string, shown as 100, with nameMatchResult GOOD_MATCH. Parse it to a number before comparing, and do not hard-code the set of labels from one example. Calibrate your own accept and review thresholds on real results.
Does a valid account with a matching name prove the payee is trustworthy?
No. It shows the account is real and plausibly theirs. It does not stop mule or pass-through accounts, account takeover of a legitimate payee or intent to defraud, so keep other controls such as holds and second approvals on changed accounts.
What are the sandbox values for bank account verification?
SANDBOX0000000001 returns a valid account with a sample holder and SANDBOX9999999999 returns not valid. Other inputs get a stable but arbitrary outcome. These values do not follow the production nine to eighteen digit format, so ask support if validation rejects them.
Why is bank account verification flagged as higher risk?
In StampMitra's production access form, bank account verification and e-Sign are flagged higher risk and open after a short extra check, while PAN, GSTIN, CIN, Udyam and e-Stamp are treated as lower risk. Apply for it together with your other products.
What should I store after a bank account verification?
Store the referenceId, utr, time, masked account number with the full IFSC, the score, status, your decision and reasons, and any reviewer override. Show only the last four digits of the account number in your own screens and logs.
Sources
- StampMitra API reference data for
/v1/verify/bank-account: request fields, response fields, errors, scopes and billing, as in this repository on 2026-10-29. The reference documents one example value for the status and match fields. - StampMitra sandbox test values, copied from the sandbox responder, 2026-10-29.
- StampMitra production access form: product risk flags and review steps, as implemented in this repository on 2026-10-29.
- Reserve Bank of India and National Payments Corporation of India publications on IMPS and IFSC conventions: https://www.rbi.org.in and https://www.npci.org.in (general conventions only; confirm current rules there, 2026).