ibanchecker.cash
Developer ResourcesAugust 15, 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.

Emre Arkan
Emre Arkan

Technical Support, ibanchecker.cash

rubyrailsiban-validationibanizatorbanktools-eumod-97activemodel
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 92 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.

Sources & References

Last updated: June 2026

Integrate IBAN validation via API

REST API with 1,000 free validations/month. JSON response with bank name, SEPA status, and BBAN breakdown.