Para processar consultas de CPF em segundo plano no Rails sem bloquear requisições HTTP, crie um ConsultarCpfJob com ActiveJob e Sidekiq: o controller responde imediatamente com status 202, o job consulta a API da CPFHub.io em background com retry automático e persiste o resultado no banco — suportando desde validações individuais no checkout até lotes de milhares de CPFs com filas priorizadas.
Introdução
Em aplicações Rails, processar consultas de CPF de forma síncrona durante uma requisição HTTP pode causar timeouts e degradar a experiência do usuário. O ActiveJob, framework nativo do Rails para processamento em background, oferece uma abstração elegante que funciona com diversos backends como Sidekiq, Resque e Delayed Job.
Configuração do ActiveJob com Sidekiq
Primeiro, configure o Rails para utilizar o Sidekiq como backend do ActiveJob.
# Gemfile
gem "sidekiq", "~> 7.0"
gem "faraday", "~> 2.0"
gem "faraday-retry", "~> 2.0"
# config/application.rb
config.active_job.queue_adapter = :sidekiq
# config/sidekiq.yml
:concurrency: 10
:queues:
- [consulta_cpf_urgente, 3]
- [consulta_cpf_padrao, 2]
- [consulta_cpf_lote, 1]
| Fila | Prioridade | Uso |
|---|---|---|
| consulta_cpf_urgente | Alta (3) | Checkout em tempo real |
| consulta_cpf_padrao | Média (2) | Cadastros e validações regulares |
| consulta_cpf_lote | Baixa (1) | Processamento em massa |
Criando o job de consulta de CPF
O job encapsula toda a lógica de consulta à API, tratamento de erros e persistência do resultado.
# app/jobs/consultar_cpf_job.rb
class ConsultarCpfJob < ApplicationJob
queue_as :consulta_cpf_padrao
retry_on Faraday::TimeoutError, wait: :exponentially_longer, attempts: 5
retry_on Faraday::ConnectionFailed, wait: 30.seconds, attempts: 3
discard_on ActiveJob::DeserializationError
before_perform :registrar_inicio
after_perform :registrar_conclusao
def perform(cpf, callback_class: nil, callback_id: nil)
cliente_api = criar_cliente_api
resposta = cliente_api.get("/cpf/#{cpf}")
resultado = JSON.parse(resposta.body)
if resultado["success"]
salvar_resultado(cpf, resultado["data"])
executar_callback(callback_class, callback_id, resultado["data"])
else
registrar_falha(cpf, "CPF nao encontrado na base")
end
rescue Faraday::ClientError => e
registrar_falha(cpf, "Erro na API: #{e.message}")
raise
end
private
def criar_cliente_api
Faraday.new(url: "https://api.cpfhub.io") do |conn|
conn.request :retry, max: 2, interval: 1, backoff_factor: 2
conn.headers["x-api-key"] = ENV["CPFHUB_API_KEY"]
conn.headers["Content-Type"] = "application/json"
conn.options.timeout = 10
conn.options.open_timeout = 5
conn.adapter Faraday.default_adapter
end
end
def salvar_resultado(cpf, dados)
ConsultaCpf.create!(
cpf: cpf,
nome: dados["name"],
genero: dados["gender"],
data_nascimento: dados["birthDate"],
status: "sucesso",
consultado_em: Time.current
)
end
def executar_callback(callback_class, callback_id, dados)
return unless callback_class && callback_id
klass = callback_class.constantize
registro = klass.find(callback_id)
registro.cpf_validado!(dados)
end
def registrar_inicio
Rails.logger.info("[ConsultarCpfJob] Iniciando consulta")
end
def registrar_conclusao
Rails.logger.info("[ConsultarCpfJob] Consulta concluida")
end
def registrar_falha(cpf, motivo)
ConsultaCpf.create!(
cpf: cpf,
status: "falha",
motivo_falha: motivo,
consultado_em: Time.current
)
end
end
Enfileirando jobs a partir de controllers
O controller recebe a requisição e enfileira o job, respondendo imediatamente ao cliente.
# app/controllers/api/cpf_controller.rb
module Api
class CpfController < ApplicationController
def consultar
cpf = params[:cpf].gsub(/\D/, "")
unless cpf_valido?(cpf)
render json: { erro: "CPF invalido" }, status: :unprocessable_entity
return
end
# Verificar se já existe no cache
consulta_existente = ConsultaCpf.recente.find_by(cpf: cpf)
if consulta_existente&.sucesso?
render json: {
fonte: "cache",
dados: consulta_existente.as_json
}
return
end
# Enfileirar para processamento em background
job = ConsultarCpfJob.perform_later(cpf)
render json: {
mensagem: "Consulta enfileirada com sucesso",
job_id: job.provider_job_id,
status: "pendente"
}, status: :accepted
end
def consultar_lote
cpfs = params[:cpfs]
cpfs.each_with_index do |cpf, index|
ConsultarCpfJob
.set(queue: :consulta_cpf_lote, wait: index.seconds)
.perform_later(cpf.gsub(/\D/, ""))
end
render json: {
mensagem: "#{cpfs.size} CPFs enfileirados para processamento",
status: "pendente"
}, status: :accepted
end
private
def cpf_valido?(cpf)
cpf.match?(/\A\d{11}\z/) && !cpf.match?(/\A(\d)\1{10}\z/)
end
end
end
Model e migration para armazenar resultados
Crie o model e a migration para persistir os resultados das consultas.
# db/migrate/XXXXXX_create_consultas_cpf.rb
class CreateConsultasCpf < ActiveRecord::Migration[7.1]
def change
create_table :consultas_cpf do |t|
t.string :cpf, null: false, index: true
t.string :nome
t.string :genero
t.date :data_nascimento
t.string :status, null: false, default: "pendente"
t.text :motivo_falha
t.datetime :consultado_em
t.timestamps
end
add_index :consultas_cpf, [:cpf, :created_at]
add_index :consultas_cpf, :status
end
end
# app/models/consulta_cpf.rb
class ConsultaCpf < ApplicationRecord
validates :cpf, presence: true
validates :status, inclusion: {
in: %w[pendente sucesso falha]
}
scope :recente, -> { where("created_at > ?", 24.hours.ago) }
scope :sucesso, -> { where(status: "sucesso") }
scope :falha, -> { where(status: "falha") }
def sucesso?
status == "sucesso"
end
end
| Campo | Tipo | Descrição |
|---|---|---|
| cpf | string | CPF consultado (11 dígitos) |
| nome | string | Nome retornado pela API |
| genero | string | Gênero retornado pela API |
| data_nascimento | date | Data de nascimento retornada |
| status | string | pendente, sucesso ou falha |
| motivo_falha | text | Motivo em caso de falha |
| consultado_em | datetime | Momento da consulta à API |
Monitoramento e métricas
Monitore os jobs para garantir que o processamento está saudável.
# app/services/metricas_cpf_service.rb
class MetricasCpfService
def self.resumo
{
total_consultas: ConsultaCpf.count,
sucessos: ConsultaCpf.sucesso.count,
falhas: ConsultaCpf.falha.count,
taxa_sucesso: calcular_taxa_sucesso,
filas: {
urgente: Sidekiq::Queue.new("consulta_cpf_urgente").size,
padrao: Sidekiq::Queue.new("consulta_cpf_padrao").size,
lote: Sidekiq::Queue.new("consulta_cpf_lote").size
},
workers_ativos: Sidekiq::Workers.new.size
}
end
def self.calcular_taxa_sucesso
total = ConsultaCpf.count
return 0 if total.zero?
((ConsultaCpf.sucesso.count.to_f / total) * 100).round(2)
end
end
Perguntas frequentes
Por que usar ActiveJob em vez de processar consultas de CPF de forma síncrona no controller?
Consultas síncronas à API externa dentro de uma requisição HTTP podem demorar até 1-2 segundos em condições normais — e muito mais em picos ou instabilidade de rede. Com ActiveJob e Sidekiq, o controller responde em milissegundos e o processamento real ocorre no worker em background, sem afetar o tempo de resposta da aplicação para o usuário.
Como configurar o número de workers Sidekiq para processar consultas de CPF?
Defina concurrency no sidekiq.yml de acordo com o volume esperado. Para o plano Pro da CPFHub.io (1.000 consultas/mês), uma concorrência de 5 a 10 workers já é suficiente. Se o volume ultrapassar o limite do plano, a API não bloqueia — cobra R$0,15 por consulta adicional. Escale os workers conforme o volume crescer.
Como garantir conformidade com a LGPD ao armazenar resultados de consultas de CPF no banco?
Armazene apenas os campos necessários para a finalidade declarada, não o CPF em texto puro se um identificador interno bastar, implemente controle de acesso às tabelas de consulta e defina política de retenção com exclusão automática após o prazo necessário. A ANPD orienta que o princípio da necessidade se aplica tanto à coleta quanto ao armazenamento de dados pessoais.
O que acontece se o Sidekiq reiniciar durante o processamento de um job de CPF?
O Sidekiq usa o Redis para rastrear jobs em andamento. Se um worker for interrompido durante o processamento, o job retorna à fila após o timeout de visibilidade e é retentado automaticamente conforme a configuração de retry_on no job. Para evitar registros duplicados no banco, verifique a existência do CPF antes de salvar (upsert ou find_or_create_by).
Conclusão
O ActiveJob com Sidekiq oferece uma solução elegante e robusta para processar consultas de CPF em segundo plano no Rails. Com retry automático, filas priorizadas e persistência de resultados, o sistema garante que nenhuma consulta seja perdida e que a aplicação responda rapidamente ao usuário. A separação entre produtores e consumidores permite escalar o processamento de forma independente da aplicação web.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar consultas de CPF em background na sua aplicação Rails hoje mesmo.
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.



