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.
Technical Support, ibanchecker.cash
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.
- Format check: Two uppercase letters (country code), two digits (check digits), alphanumeric BBAN. Strip spaces and dashes before checking.
- 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.
- 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?
# => falseThe 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
endHow 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
endSee the API documentation for authentication details and the pricing page for rate limits on each plan.
Sources & References
- SWIFT IBAN Registry — Official IBAN country format specifications
- ISO 13616-1:2020 — International standard for IBAN structure
- ibanizator (RubyGems) — Pure Ruby IBAN validation gem
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.
How to Validate IBANs in JavaScript (API & Manual)
Two approaches: call the ibanchecker.cash API for full bank data, or implement MOD-97 validation yourself. Includes work...
IBAN Validation in Python: Complete Implementation with Regex and Check Digits
Two production-ready approaches: call the ibanchecker.cash API for bank metadata, or implement the MOD-97 algorithm your...
Building an IBAN Validation API: REST Endpoint Design and Edge Cases
Architect-level guide to IBAN API design — GET vs POST tradeoffs, error code standards, normalization contracts, KV cach...
IBAN Regex Patterns That Actually Work (And Why Simple Regex Isn't Enough)
Country-specific regex patterns for DE, GB, FR, NL plus a generic IBAN regex — and why regex is only step 1 of validatio...