Ruby · Email breach checks
Check email breaches in Ruby with net/http
A breach lookup in Ruby is one POST to /v1/breaches/email followed by a walk over the breaches array. Each entry is a hash with string keys, so you read breach["Title"], breach["BreachDate"] and breach["DataClasses"] straight from the parsed JSON. The script prints every breach for an address, marks entries whose IsVerified flag is false, and lists the data classes that leaked, which is what you need to decide whether to prompt a password reset.
Error handling lives in a case statement on the response class. Net::HTTPBadRequest means the address failed Finowl's validation, Net::HTTPUnauthorized points at the API key, and Net::HTTPTooManyRequests either sleeps for the Retry-After seconds or stops when the error code is quota_exceeded. In a Rails app, move the method into a service object and call it from Active Job so signups never wait on the network.
Steps
- 1
Create an API key in the Finowl dashboard at https://app.finowl.co and export it as FINOWL_API_KEY.
- 2
Save the script as email_breaches.rb; it only requires net/http and json from the standard library.
- 3
Run ruby email_breaches.rb someone@example.com to check the address passed as the first argument.
- 4
Inspect DataClasses on each breach to decide whether the user needs a password reset or just a notice.
- 5
Move email_breaches into a service object or job class once the output looks right.
require "json"
require "net/http"
ENDPOINT = URI("https://api.finowl.co/v1/breaches/email")
def email_breaches(email, attempts: 3)
attempts.times do
response = Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: true,
open_timeout: 5, read_timeout: 15) do |http|
request = Net::HTTP::Post.new(ENDPOINT)
request["Authorization"] = "Bearer #{ENV.fetch("FINOWL_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = JSON.generate(email: email)
http.request(request)
end
case response
when Net::HTTPSuccess
return JSON.parse(response.body)
when Net::HTTPBadRequest
raise ArgumentError, "Finowl rejected #{email.inspect} as invalid_input"
when Net::HTTPUnauthorized
raise "FINOWL_API_KEY is missing or invalid"
when Net::HTTPTooManyRequests
code = JSON.parse(response.body).dig("error", "code")
raise "Finowl credits are used up" if code == "quota_exceeded"
sleep [response["Retry-After"].to_i, 1].max
else
raise "Finowl returned HTTP #{response.code}; not retrying"
end
end
raise "Still rate limited after #{attempts} attempts"
end
result = email_breaches(ARGV.fetch(0, "alex@example.com"))
if result["breached"]
puts "#{result["email"]} appears in #{result["breaches"].size} breach(es):"
result["breaches"].each do |breach|
note = breach["IsVerified"] ? "" : " (unverified)"
puts "- #{breach["Title"]}, #{breach["BreachDate"]}#{note}"
puts " exposed: #{breach["DataClasses"].join(", ")}"
end
else
puts "No known breaches for #{result["email"]}"
endIN PRODUCTION
Production tips
Normalize addresses with strip and downcase before the lookup, and cache each result for a while so repeated logins from the same person do not spend a fresh credit every time.
Treat entries with IsVerified set to false as weaker signals. Mention them to users with softer wording, or leave them out of automated actions such as forced password resets.
Send email lookups from Sidekiq or Active Job rather than the request cycle. A job can sleep on Retry-After without holding a Puma thread, and slow responses never turn into slow signups.
Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.
A FEW GOOD QUESTIONS
Common questions
Related guides
- Check passwords against breach data in Ruby
- Predict gender from first names in Ruby
- Check email breaches in Python with requests
- Check email breaches in Node.js with fetch
- Check email breaches in TypeScript with typed results
- Check email breaches in Go with net/http
- Email breach checks for fintech signups and logins
- Email breach checks for online stores and shopper logins
- Email breach checks for SaaS logins and admins
Check email breaches in Ruby with net/http
Grab an API key, paste the example and run it. 100 free credits every month.