ibanchecker.cash
Developer ResourcesJune 13, 2026 · 8 min read

IBAN Validation in Ruby — Gems, Code Examples & Best Practices

How to validate IBANs in Ruby using ibanizator and banktools-eu gems, a pure-Ruby MOD-97 implementation, Rails model validator, and calling the ibanchecker.cash REST API for bank metadata.

Share

IBAN validation in Ruby is a common requirement for fintech Rails applications, payment processing gems, and data import pipelines. This guide covers three approaches: using the ibanizator gem, using banktools-eu, and calling the ibanchecker.cash REST API when you need bank metadata alongside the validity result.

What Are the Validation Layers Every Ruby IBAN Checker Must Cover?

IBAN validation is a three-step process. A regex alone is not sufficient because it cannot verify the MOD-97 check digit embedded in positions 3–4 of every IBAN.

  1. Format check: Two uppercase letters (country code), two digits (check digits), alphanumeric BBAN. Strip spaces and dashes before checking.
  2. Length check: Each country has a fixed length. German IBANs are always 22 characters; UK IBANs are always 22; French IBANs are always 27. Reject anything that deviates.
  3. MOD-97 check: Move the first four characters to the end, replace each letter A–Z with its numeric equivalent (A=10 … Z=35), interpret the result as a large integer, and compute modulo 97. Valid IBANs produce a remainder of 1.

Which Ruby Gems Should You Use for IBAN Validation?

Two well-maintained gems cover all production use cases:

  • ibanizator — pure Ruby, zero dependencies, covers all 84 IBAN countries, returns structured objects with country and BBAN.
  • banktools-eu — focuses on European IBANs, also parses BIC and branch codes from the BBAN.

For most Rails applications, ibanizator is the correct default.

Using ibanizator

# Gemfile
gem "ibanizator"

# In your code
require "ibanizator"

iban = Ibanizator.iban_from_string("DE89 3704 0044 0532 0130 00")

puts iban.valid?
# => true

puts iban.country_code
# => :de

puts iban.bban
# => "370400440532013000"

invalid = Ibanizator.iban_from_string("DE00370400440532013000")
puts invalid.valid?
# => false

The gem normalizes whitespace automatically — you can pass either the electronic format (DE89370400440532013000) or the printed format with spaces.

Using banktools-eu

# Gemfile
gem "banktools-eu"

require "banktools-eu"

iban = BankTools::EU::IBAN.new("GB29NWBK60161331926819")

puts iban.valid?
# => true

puts iban.errors
# => []

invalid = BankTools::EU::IBAN.new("GB29NWBK6016133192681X")
puts invalid.valid?
# => false

puts invalid.errors
# => [:invalid_characters]

How Do You Implement MOD-97 Validation in Pure Ruby?

If you prefer no gem dependencies — useful in data migration scripts or lambdas where bundle size matters — implement the algorithm directly. Ruby handles arbitrarily large integers natively, so there is no BigInt equivalent needed.

IBAN_LENGTHS = {
  "AD" => 24, "AE" => 23, "AL" => 28, "AT" => 20, "AZ" => 28,
  "BA" => 20, "BE" => 16, "BG" => 22, "BH" => 22, "BR" => 29,
  "BY" => 28, "CH" => 21, "CR" => 22, "CY" => 28, "CZ" => 24,
  "DE" => 22, "DK" => 18, "DO" => 28, "EE" => 20, "EG" => 29,
  "ES" => 24, "FI" => 18, "FO" => 18, "FR" => 27, "GB" => 22,
  "GE" => 22, "GI" => 23, "GL" => 18, "GR" => 27, "GT" => 28,
  "HR" => 21, "HU" => 28, "IE" => 22, "IL" => 23, "IQ" => 23,
  "IS" => 26, "IT" => 27, "JO" => 30, "KW" => 30, "KZ" => 20,
  "LB" => 28, "LC" => 32, "LI" => 21, "LT" => 20, "LU" => 20,
  "LV" => 21, "LY" => 25, "MC" => 27, "MD" => 24, "ME" => 22,
  "MK" => 19, "MR" => 27, "MT" => 31, "MU" => 30, "NL" => 18,
  "NO" => 15, "PK" => 24, "PL" => 28, "PS" => 29, "PT" => 25,
  "QA" => 29, "RO" => 24, "RS" => 22, "SA" => 24, "SC" => 31,
  "SE" => 24, "SI" => 19, "SK" => 24, "SM" => 27, "ST" => 25,
  "SV" => 28, "TL" => 23, "TN" => 24, "TR" => 26, "UA" => 29,
  "VA" => 22, "VG" => 24, "XK" => 20
}.freeze

def normalize_iban(raw)
  raw.strip.gsub(/[s-]/, "").upcase
end

def validate_iban(raw)
  iban = normalize_iban(raw)

  return { valid: false, error: "Invalid format" } unless iban.match?(/A[A-Z]{2}[0-9]{2}[A-Z0-9]+z/)

  country = iban[0, 2]
  expected = IBAN_LENGTHS[country]

  return { valid: false, error: "Unknown country: #{country}" } unless expected
  return { valid: false, error: "Wrong length: expected #{expected}, got #{iban.length}" } if iban.length != expected

  rearranged = iban[4..] + iban[0, 4]
  numeric = rearranged.chars.map { |c| c =~ /[A-Z]/ ? (c.ord - 55).to_s : c }.join

  if numeric.to_i % 97 != 1
    return { valid: false, error: "Check digit validation failed (MOD-97)" }
  end

  { valid: true }
end

p validate_iban("DE89 3704 0044 0532 0130 00")
# => {:valid=>true}

p validate_iban("DE89370400440532013001")
# => {:valid=>false, :error=>"Check digit validation failed (MOD-97)"}

How Do You Integrate IBAN Validation in a Rails Model?

Add a custom validator that uses the gem and optionally calls the API for bank metadata enrichment. The local check runs synchronously in the model; the API call is best deferred to a background job to avoid blocking web requests.

# app/validators/iban_validator.rb
require "ibanizator"

class IbanValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    iban = Ibanizator.iban_from_string(value.to_s)
    unless iban.valid?
      record.errors.add(attribute, options[:message] || "is not a valid IBAN")
    end
  end
end

# app/models/bank_account.rb
class BankAccount < ApplicationRecord
  validates :iban, presence: true, iban: true

  before_save :normalize_iban

  private

  def normalize_iban
    self.iban = iban.to_s.strip.gsub(/[s-]/, "").upcase if iban.present?
  end
end

How Do You Call the ibanchecker.cash API from Ruby?

Use Net::HTTP for stdlib-only environments, or Faraday/HTTParty in Rails applications. The API returns bank name, BIC, and country alongside the validity flag — useful for autofilling bank name fields in payment forms.

require "net/http"
require "json"

def validate_iban_api(iban)
  uri = URI("https://ibanchecker.cash/api/v1/validate")
  req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
  req.body = { iban: iban }.to_json

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  JSON.parse(res.body, symbolize_names: true)
end

result = validate_iban_api("DE89370400440532013000")
puts result
# {valid: true, iban: "DE89370400440532013000", country: "Germany",
#  bankName: "Deutsche Bank", bic: "DEUTDEDB", formatted: "DE89 3704 0044 0532 0130 00"}

For bulk validation of up to 100 IBANs in one request:

def validate_ibans_bulk(ibans)
  uri = URI("https://ibanchecker.cash/api/v1/validate/bulk")
  req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
  req.body = { ibans: ibans }.to_json

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  JSON.parse(res.body, symbolize_names: true)[:results]
end

results = validate_ibans_bulk([
  "DE89370400440532013000",
  "GB29NWBK60161331926819",
  "INVALID",
])

results.each { |r| puts "#{r[:iban]}: #{r[:valid]}" }

How Should You Test IBAN Validation in RSpec?

# spec/validators/iban_validator_spec.rb
RSpec.describe IbanValidator do
  let(:record) { BankAccount.new }

  VALID_IBANS = %w[
    DE89370400440532013000
    GB29NWBK60161331926819
    FR7630006000011234567890189
    NL91ABNA0417164300
    BE68539007547034
  ].freeze

  INVALID_IBANS = %w[
    DE89370400440532013001
    GB29NWBK6016133192681
    NOTANIBAN
    DE00000000000000000000
  ].freeze

  VALID_IBANS.each do |iban|
    it "accepts #{iban}" do
      record.iban = iban
      record.valid?
      expect(record.errors[:iban]).to be_empty
    end
  end

  INVALID_IBANS.each do |iban|
    it "rejects #{iban}" do
      record.iban = iban
      record.valid?
      expect(record.errors[:iban]).not_to be_empty
    end
  end

  it "normalizes spaces before validation" do
    record.iban = "DE89 3704 0044 0532 0130 00"
    record.valid?
    expect(record.errors[:iban]).to be_empty
    expect(record.iban).to eq("DE89370400440532013000")
  end
end

See the API documentation for authentication details and the pricing page for rate limits on each plan.

Last updated: June 2026

Validate an IBAN instantly

Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.

Open IBAN Checker →

Related Articles