Para consumir a API de CPF da CPFHub.io em Ruby on Rails com Faraday, crie um initializer que configura o cliente HTTP com timeout, retry para erros 5xx e a chave de API via Rails.application.credentials. Em seguida, encapsule a lógica em um service object e adicione tratamento de exceções granular — a API nunca retorna HTTP 429, então o retry deve ser reservado apenas para falhas transitórias do servidor. A ANPD recomenda que credenciais de APIs que acessam dados pessoais sejam armazenadas de forma criptografada, o que as credentials do Rails atendem nativamente.
Introdução
Ruby on Rails continua sendo um framework popular para desenvolvimento web rápido, especialmente em startups e empresas que valorizam produtividade e convenção sobre configuração. Para aplicações Rails que operam no mercado brasileiro, a validação de CPF é um requisito recorrente em processos de cadastro, checkout e onboarding.
A gem Faraday é a biblioteca HTTP mais utilizada no ecossistema Ruby para consumo de APIs REST, oferecendo uma interface flexível com suporte a middleware, retry e timeout.
Pré-requisitos
- Ruby 3.2+ — versão recomendada.
- Rails 7.1+ — framework web.
- Conta na CPFHub.io — para obter a chave de API (50 consultas/mês no plano gratuito).
Adicionando a gem Faraday
No Gemfile:
gem 'faraday', '~> 2.9'
gem 'faraday-retry', '~> 2.2'
Execute bundle install.
Configuração da chave de API
Usando credentials do Rails
rails credentials:edit
Adicione:
cpfhub:
api_key: SUA_CHAVE_DE_API
Ou via variável de ambiente
No arquivo .env (com a gem dotenv-rails):
CPFHUB_API_KEY=SUA_CHAVE_DE_API
Criando o cliente HTTP com Faraday
Crie um initializer para configurar o cliente Faraday:
# config/initializers/cpfhub.rb
module CpfHub
BASE_URL = 'https://api.cpfhub.io'
def self.client
@client ||= Faraday.new(url: BASE_URL) do |conn|
conn.request :retry, max: 2, interval: 1, backoff_factor: 2,
retry_statuses: [500, 502, 503]
conn.headers['x-api-key'] = api_key
conn.headers['Accept'] = 'application/json'
conn.options.timeout = 10
conn.options.open_timeout = 5
conn.adapter Faraday.default_adapter
end
end
def self.api_key
Rails.application.credentials.dig(:cpfhub, :api_key) ||
ENV.fetch('CPFHUB_API_KEY', '')
end
end
Note que retry_statuses inclui apenas erros 5xx — a CPFHub.io nunca retorna HTTP 429. Ao ultrapassar a cota do plano, cada consulta adicional é cobrada a R$0,15 e a API continua respondendo normalmente.
Criando o service object
Seguindo as convenções do Rails, encapsule a lógica de consulta em um service object:
# app/services/cpf_validation_service.rb
class CpfValidationService
class CpfInvalidoError < StandardError; end
class CpfNaoEncontradoError < StandardError; end
class ApiIndisponivel < StandardError; end
class TimeoutError < StandardError; end
def initialize(cpf)
@cpf = cpf.to_s.gsub(/\D/, '')
end
def call
validar_formato!
consultar_api
end
private
def validar_formato!
unless @cpf.match?(/\A\d{11}\z/)
raise CpfInvalidoError, 'CPF inválido. Informe 11 dígitos numéricos.'
end
end
def consultar_api
response = CpfHub.client.get("/cpf/#{@cpf}")
case response.status
when 200
processar_resposta(response.body)
when 401
raise StandardError, 'Chave de API inválida ou ausente.'
when 500, 503
raise ApiIndisponivel, "Serviço temporariamente indisponível (HTTP #{response.status})."
else
raise StandardError, "Erro HTTP inesperado: #{response.status}"
end
rescue Faraday::TimeoutError
raise TimeoutError, 'A consulta excedeu o tempo limite.'
rescue Faraday::ConnectionFailed => e
raise StandardError, "Erro de conexão: #{e.message}"
end
def processar_resposta(body)
dados = JSON.parse(body)
if dados['success'] && dados['data']
OpenStruct.new(
cpf: dados['data']['cpf'],
nome: dados['data']['name'],
nome_upper: dados['data']['nameUpper'],
genero: dados['data']['gender'],
data_nascimento: dados['data']['birthDate'],
dia: dados['data']['day'],
mes: dados['data']['month'],
ano: dados['data']['year']
)
else
raise CpfNaoEncontradoError, 'CPF não encontrado na base de dados.'
end
end
end
Criando o controller
# app/controllers/cpf_controller.rb
class CpfController < ApplicationController
def show
resultado = CpfValidationService.new(params[:cpf]).call
render json: {
valido: true,
dados: {
cpf: resultado.cpf,
nome: resultado.nome,
genero: resultado.genero,
data_nascimento: resultado.data_nascimento
}
}
rescue CpfValidationService::CpfInvalidoError => e
render json: { erro: e.message }, status: :bad_request
rescue CpfValidationService::CpfNaoEncontradoError => e
render json: { erro: e.message }, status: :not_found
rescue CpfValidationService::TimeoutError => e
render json: { erro: e.message }, status: :gateway_timeout
rescue StandardError => e
render json: { erro: e.message }, status: :internal_server_error
end
end
Rota
# config/routes.rb
Rails.application.routes.draw do
get 'cpf/:cpf', to: 'cpf#show', constraints: { cpf: /\d{11}/ }
end
Usando em um model com validação customizada
Para validar o CPF ao salvar um registro no banco de dados:
# app/models/cliente.rb
class Cliente < ApplicationRecord
validate :cpf_valido_na_api, if: -> { cpf_changed? && cpf.present? }
private
def cpf_valido_na_api
resultado = CpfValidationService.new(cpf).call
self.nome_receita = resultado.nome
rescue CpfValidationService::CpfInvalidoError
errors.add(:cpf, 'é sintaticamente inválido')
rescue CpfValidationService::CpfNaoEncontradoError
errors.add(:cpf, 'não foi encontrado na base de dados')
rescue CpfValidationService::TimeoutError
errors.add(:base, 'Não foi possível validar o CPF no momento.')
rescue StandardError
errors.add(:base, 'Erro ao validar CPF.')
end
end
Exemplo de resposta da API
{
"success": true,
"data": {
"cpf": "12345678900",
"name": "João da Silva",
"nameUpper": "JOÃO DA SILVA",
"gender": "M",
"birthDate": "15/06/1990",
"day": 15,
"month": 6,
"year": 1990
}
}
Testando com cURL
curl -X GET https://api.cpfhub.io/cpf/12345678900 \
-H "x-api-key: SUA_CHAVE_DE_API" \
-H "Accept: application/json" \
--max-time 10
Para testar o endpoint Rails:
curl -X GET http://localhost:3000/cpf/12345678900 \
-H "Accept: application/json" \
--max-time 10
Escrevendo testes com RSpec
# spec/services/cpf_validation_service_spec.rb
require 'rails_helper'
RSpec.describe CpfValidationService do
describe '#call' do
context 'com CPF válido' do
before do
stub_request(:get, 'https://api.cpfhub.io/cpf/12345678900')
.to_return(
status: 200,
body: {
success: true,
data: {
cpf: '12345678900',
name: 'João da Silva',
nameUpper: 'JOÃO DA SILVA',
gender: 'M',
birthDate: '15/06/1990',
day: 15,
month: 6,
year: 1990
}
}.to_json,
headers: { 'Content-Type' => 'application/json' }
)
end
it 'retorna os dados do CPF' do
resultado = described_class.new('12345678900').call
expect(resultado.nome).to eq('João da Silva')
expect(resultado.cpf).to eq('12345678900')
end
end
context 'com CPF inválido' do
it 'levanta CpfInvalidoError' do
expect {
described_class.new('123').call
}.to raise_error(CpfValidationService::CpfInvalidoError)
end
end
end
end
Boas práticas
-
Service objects — isole a lógica de integração em service objects para facilitar testes e manutenção.
-
Timeout — configure
timeouteopen_timeoutno Faraday para evitar requisições travadas. -
Retry — use o middleware
faraday-retrypara lidar com falhas transitórias 5xx automaticamente. Não é necessário retry por cota: a CPFHub.io nunca bloqueia, cobra R$0,15/extra ao ultrapassar o plano. -
Credentials — armazene a chave de API nas credentials criptografadas do Rails ou em variáveis de ambiente.
-
Testes — use WebMock ou VCR para mockar requisições HTTP nos testes, evitando chamadas reais à API.
Perguntas frequentes
O Faraday é obrigatório para consumir a API de CPF em Rails?
Não. Qualquer biblioteca HTTP funciona — Net::HTTP, HTTParty, Typhoeus. O Faraday é recomendado porque seu sistema de middleware torna simples adicionar retry, logging e autenticação de forma desacoplada. Para projetos que já usam outra biblioteca, adaptar os exemplos deste guia é direto.
Como configurar o retry do Faraday corretamente para a CPFHub.io?
Configure retry_statuses: [500, 502, 503] e omita o código 429, já que a CPFHub.io nunca o retorna. Um max: 2 com backoff_factor: 2 cobre bem instabilidades transitórias sem atrasar o fluxo da aplicação. O intervalo resultante é 1s na primeira tentativa e 2s na segunda.
É seguro validar o CPF dentro de um callback do ActiveRecord?
Sim, mas com ressalvas. Callbacks before_validation ou validate que fazem chamadas externas aumentam o tempo de resposta do save. Para operações em lote ou importações, prefira validar CPFs em background jobs e usar o resultado armazenado no model. Para cadastros em tempo real, a chamada síncrona é adequada dado que a latência da API é ~900ms.
Como lidar com o timeout da API em um fluxo de checkout?
Defina um timeout de 5 a 10 segundos no Faraday e trate CpfValidationService::TimeoutError no controller com uma resposta que permita ao usuário tentar novamente. Em checkouts críticos, considere validar o CPF antes do início do fluxo de pagamento para não interromper a transação no momento do processamento.
Conclusão
Consumir a API de consulta de CPF da CPFHub.io em Ruby on Rails com Faraday é simples quando a arquitetura segue as convenções do framework: initializer para o cliente, service object para a lógica de negócio e RSpec com WebMock para os testes. O resultado é uma integração testável, resiliente e fácil de manter.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e adicione validação de CPF à sua aplicação Rails em menos de uma hora.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



