Guide · Node.js · SDK · Fintech

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.

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.

ℹ️SEPA Direct Debit rejections come with return fees (€0.50–€5.00 per rejection depending on the bank). Pre-validating IBANs at onboarding eliminates the most common rejection cause.

2. The four payment identifiers

ValidatorWhat it validatesWhen to useAPI endpoint
IBANMOD-97 checksum + bank identification + SEPA membershipBank account onboarding (SEPA / international)GET /v0/iban
BIC/SWIFTISO 9362 format + bank identificationSWIFT payment routing, cross-border transfersGET /v0/bic
Credit CardLuhn checksum + BIN lookup (network, issuer, card type)Card onboarding (Visa, Mastercard, Amex, etc.)GET /v0/credit-card
Sort CodeUK 6-digit sort code formatUK bank account onboarding (Faster Payments, BACS)GET /v0/sort-code
⚠️UK accounts use sort code + account number, not IBAN (though IBANs exist for UK accounts). Continental European accounts use IBAN + BIC. Design your form to collect the right identifiers per region.

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

1

valid === true — the IBAN passes format, length, and mod-97 checksum

2

isSEPA — required for SEPA Direct Debit mandates; false means only credit transfer is possible

3

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

1

valid === true — the BIC passes ISO 9362 format validation

2

countryCode — cross-check with the IBAN country code; a mismatch is a data entry error

3

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

1

valid === true — the card number passes Luhn and format validation

2

network — confirm the card network is accepted by your payment processor

3

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

1

valid === true — the 6-digit sort code is a valid UK bank code format

2

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');
The adaptive pattern only calls the APIs you need. A UK bank account needs IBAN + sort code. A SEPA account needs IBAN + BIC. A card onboarding needs credit card only. All run in parallel via 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

Validate IBAN + BIC together — cross-check that the BIC country matches the IBAN country
Check isSEPA for IBAN — Direct Debit mandates require a SEPA-zone account
Run Luhn check on cards before storing — reject invalid numbers at entry, not at charge time
Validate sort code for UK bank accounts — required for Faster Payments and BACS
Do not rely on card network logos — use BIN lookup to determine the actual network
Do not store raw card numbers — validate first, then tokenize with your payment processor

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.