Fintech Onboarding — Payment Method Validation
Four payment identifiers, one onboarding flow. Here's how to validate an IBAN, BIC/SWIFT code, credit card, and UK sort code when onboarding a new payment method — before it touches your payment processor.
In this guide
- 1. The payment onboarding validation problem
- 2. The four payment identifiers
- 3. Step 1: Validate the IBAN
- 4. Step 2: Validate the BIC/SWIFT
- 5. Step 3: Validate the credit card
- 6. Step 4: Validate the UK sort code
- 7. Putting it all together — adaptive parallel validation
- 8. cURL examples
- 9. Handling edge cases
- 10. Summary checklist
1. The payment onboarding validation problem
Fintech platforms onboard payment methods from users across different regions and payment networks. A UK user adding a bank account needs their sort code and account number validated. A continental European user adding a bank account needs their IBAN and BIC confirmed. A user adding a card needs Luhn checksum validation and network identification.
Invalid payment method data causes failed transactions, support tickets, and in the case of SEPA Direct Debit, return fees from the bank. Validating at the point of entry — before anything touches your payment processor — eliminates these failures entirely.
The key insight: the right validators depend on the payment method and the user's region. Run only what applies, and run them all in parallel.
2. The four payment identifiers
| Validator | What it validates | When to use | API endpoint |
|---|---|---|---|
| IBAN | MOD-97 checksum + bank identification + SEPA membership | Bank account onboarding (SEPA / international) | GET /v0/iban |
| BIC/SWIFT | ISO 9362 format + bank identification | SWIFT payment routing, cross-border transfers | GET /v0/bic |
| Credit Card | Luhn checksum + BIN lookup (network, issuer, card type) | Card onboarding (Visa, Mastercard, Amex, etc.) | GET /v0/credit-card |
| Sort Code | UK 6-digit sort code format | UK bank account onboarding (Faster Payments, BACS) | GET /v0/sort-code |
3. Step 1: Validate the IBAN
The IBAN endpoint performs multi-layer validation:
- MOD-97 checksum — the ISO 13616 algorithm that catches transcription errors
- Country-specific format — validates the correct length and BBAN structure per country
- Bank identification — returns bank name and BIC for SEPA routing
- SEPA membership — confirms the account is in a SEPA-participating country
Example response
{ "valid": true, "countryCode": "GB", "countryName": "United Kingdom", "bban": "NWBK60161331926819", "isEU": false, "isSEPA": true, "bankCode": "NWBK", "bankName": "NATIONAL WESTMINSTER BANK PLC", "bankBic": "NWBKGB2LXXX" }
What to check in your code
valid === true — the IBAN passes format, length, and mod-97 checksum
isSEPA — required for SEPA Direct Debit mandates; false means only credit transfer is possible
bankBic — use the bank-identified BIC instead of asking the user for it separately
4. Step 2: Validate the BIC/SWIFT
When the user provides a BIC directly (for international wire transfers), the endpoint validates:
- ISO 9362 format — 8 or 11 characters: 4-letter bank code, 2-letter country, 2-char location, optional 3-char branch
- Bank identification — looks up the bank name and city from the BIC registry
- IBAN cross-check — you can verify the BIC country matches the IBAN country
Example response
{ "valid": true, "bic": "NWBKGB2LXXX", "bankCode": "NWBK", "countryCode": "GB", "locationCode": "2L", "branchCode": "XXX", "bank": "NATIONAL WESTMINSTER BANK PLC", "city": "LONDON" }
What to check in your code
valid === true — the BIC passes ISO 9362 format validation
countryCode — cross-check with the IBAN country code; a mismatch is a data entry error
bank — display the bank name to the user for confirmation before saving
5. Step 3: Validate the credit card
The credit card endpoint performs multi-layer validation:
- Luhn algorithm — ISO/IEC 7812 checksum that catches typos and transpositions
- BIN lookup — identifies the card network, issuing bank, and card type (credit/debit/prepaid)
- Card number length — validates the expected digit count for the identified network
Example response
{ "valid": true, "number": "4532015112830366", "network": "Visa", "cardType": "credit", "issuer": "JPMORGAN CHASE BANK N.A.", "country": "US", "luhn": true }
What to check in your code
valid === true — the card number passes Luhn and format validation
network — confirm the card network is accepted by your payment processor
cardType — "prepaid" cards have higher risk; flag for additional verification if needed
6. Step 4: Validate the UK sort code
UK bank accounts are identified by a 6-digit sort code and 8-digit account number. The sort code identifies the specific bank branch and is required for:
- Faster Payments — UK domestic instant payments
- BACS Direct Debit — UK direct debit system
- CHAPS — UK same-day high-value payment system
Example response
{ "valid": true, "sortCode": "601613", "formatted": "60-16-13" }
What to check in your code
valid === true — the 6-digit sort code is a valid UK bank code format
formatted — use the canonical XX-XX-XX format when displaying to users
7. Putting it all together — adaptive parallel validation
The right validators depend on the payment method type and region. The following code adapts based on accountType and region. All applicable validations run in parallel via Promise.all.
import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); async function validatePaymentMethod({ iban, // bank account (SEPA / international) bic, // SWIFT routing code cardNumber, // credit/debit card number sortCode, // UK sort code accountType, // 'bank' | 'card' region, // 'uk' | 'sepa' | 'international' }) { const calls = {}; if (accountType === 'bank' && iban) { calls.iban = iv.iban(iban); } if (bic) { calls.bic = iv.bic(bic); } if (accountType === 'card' && cardNumber) { calls.card = iv.creditCard(cardNumber); } if (region === 'uk' && sortCode) { calls.sort = iv.sortCode(sortCode); } const keys = Object.keys(calls); const results = await Promise.all(Object.values(calls)); const validated = Object.fromEntries(keys.map((k, i) => [k, results[i]])); return { iban: validated.iban ? { valid: validated.iban.valid, isSEPA: validated.iban.isSEPA ?? null, bankName: validated.iban.bankName ?? null, bankBic: validated.iban.bankBic ?? null, } : null, bic: validated.bic ? { valid: validated.bic.valid, bank: validated.bic.bank ?? null, countryCode: validated.bic.countryCode ?? null, } : null, card: validated.card ? { valid: validated.card.valid, network: validated.card.network ?? null, cardType: validated.card.cardType ?? null, } : null, sort: validated.sort ? { valid: validated.sort.valid, formatted: validated.sort.formatted ?? null, } : null, }; } // ── Example: UK bank account ──────────────────────────────────────────────── const ukResult = await validatePaymentMethod({ iban: 'GB29NWBK60161331926819', sortCode: '60-16-13', accountType: 'bank', region: 'uk', }); console.log('IBAN :', ukResult.iban?.valid ? `✓ ${ukResult.iban.bankName}` : '✗ invalid'); console.log('Sort Code:', ukResult.sort?.valid ? `✓ ${ukResult.sort.formatted}` : '✗ invalid'); // ── Example: SEPA bank account ────────────────────────────────────────────── const sepaResult = await validatePaymentMethod({ iban: 'DE89370400440532013000', bic: 'COBADEFFXXX', accountType: 'bank', region: 'sepa', }); console.log('IBAN:', sepaResult.iban?.valid ? `✓ ${sepaResult.iban.bankName} (SEPA: ${sepaResult.iban.isSEPA})` : '✗ invalid'); console.log('BIC :', sepaResult.bic?.valid ? `✓ ${sepaResult.bic.bank}` : '✗ invalid');
Promise.all.8. cURL examples
Validate an IBAN (UK account):
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/iban?value=GB29NWBK60161331926819"
Validate a BIC/SWIFT code:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/bic?value=NWBKGB2LXXX"
Validate a credit card number:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/credit-card?value=4532015112830366"
Validate a UK sort code:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/sort-code?value=601613"
9. Handling edge cases
IBAN and BIC country mismatch
The IBAN country code doesn't match the provided BIC country code. The IBAN identifies a German bank but the BIC is for a French institution — likely a data entry error.
if (ibanResult.valid && bicResult.valid) { const ibanCountry = ibanResult.countryCode; const bicCountry = bicResult.countryCode; if (ibanCountry !== bicCountry) { return { status: 'mismatch', reason: `IBAN country (${ibanCountry}) doesn't match BIC country (${bicCountry})`, }; } }
Prepaid card at high-value onboarding
The card is valid but identified as prepaid. Prepaid cards have higher chargeback rates. For high-value accounts, require a non-prepaid card.
if (cardResult.valid && cardResult.cardType === 'prepaid') { if (accountTier === 'premium') { return { status: 'rejected', reason: 'Prepaid cards are not accepted for premium accounts', }; } account.riskFlags.push('prepaid_card'); }
IBAN valid but not SEPA — cannot use Direct Debit
The IBAN passes checksum validation but isSEPA is false. Only SEPA credit transfer is possible — not Direct Debit.
if (ibanResult.valid && !ibanResult.isSEPA) { return { paymentMethods: ['sepa_credit_transfer'], excluded: ['sepa_direct_debit'], reason: `Country ${ibanResult.countryCode} is not in the SEPA zone`, }; }
10. Summary checklist
See also
Validate payment methods at onboarding
Free tier includes 100 API calls per day. IBAN, BIC, credit card, and sort code validation all included. No credit card required.