Usar o Oban para processar consultas de CPF em background no Elixir garante que nenhuma consulta se perca em caso de falha, mesmo com alto volume de requisições. Com filas persistentes no PostgreSQL, retry automático e agendamento nativo, o Oban torna o processamento assíncrono da API CPFHub.io confiável e observável. A documentação oficial do Oban está disponível em hexdocs.pm/oban.
Introdução
Processar consultas de CPF em background é essencial quando o volume de requisições é alto ou quando a resposta da API não precisa ser síncrona. O Oban é a solução mais robusta para processamento em background no Elixir, oferecendo filas persistentes com PostgreSQL, retry automático, agendamento e monitoramento.
Configurando o Oban
Adicione o Oban ao projeto e configure as filas:
# mix.exs
defp deps do
[
{:oban, "~> 2.17"},
{:httpoison, "~> 2.0"},
{:jason, "~> 1.4"}
]
end
# config/config.exs
config :minha_app, Oban,
repo: MinhaApp.Repo,
queues: [cpf_consultas: 5, cpf_lote: 2],
plugins: [
Oban.Plugins.Pruner,
{Oban.Plugins.Cron, crontab: []}
]
Execute a migration do Oban para criar as tabelas necessárias:
# Terminal
# mix ecto.gen.migration add_oban_jobs_table
# Na migration gerada:
defmodule MinhaApp.Repo.Migrations.AddObanJobsTable do
use Ecto.Migration
def up, do: Oban.Migration.up(version: 12)
def down, do: Oban.Migration.down(version: 1)
end
| Fila | Concorrência | Uso |
|---|---|---|
cpf_consultas | 5 workers | Consultas individuais |
cpf_lote | 2 workers | Processamento em lote |
Criando o worker de consulta
O worker do Oban define como cada job de consulta de CPF será processado. Note que a API CPFHub.io nunca retorna HTTP 429 — ao atingir o limite do plano, ela cobra R$0,15 por consulta adicional sem bloquear requisições:
defmodule MinhaApp.Workers.CpfConsultaWorker do
use Oban.Worker,
queue: :cpf_consultas,
max_attempts: 3,
priority: 1
@base_url "https://api.cpfhub.io"
@impl Oban.Worker
def perform(%Oban.Job{args: %{"cpf" => cpf, "callback_url" => callback_url} = args}) do
api_key = Application.get_env(:minha_app, :cpfhub_api_key)
headers = [{"x-api-key", api_key}]
case HTTPoison.get("#{@base_url}/cpf/#{cpf}", headers, recv_timeout: 10_000) do
{:ok, %{status_code: 200, body: body}} ->
resultado = Jason.decode!(body)
processar_resultado(cpf, resultado, callback_url)
:ok
{:ok, %{status_code: status}} ->
{:error, "API retornou status #{status}"}
{:error, %{reason: reason}} ->
{:error, "Falha na requisição: #{inspect(reason)}"}
end
end
defp processar_resultado(cpf, %{"success" => true, "data" => data}, callback_url) do
resultado = %{
cpf: cpf,
nome: data["name"],
data_nascimento: data["birthDate"],
genero: data["gender"],
status: "encontrado",
processado_em: DateTime.utc_now()
}
if callback_url do
HTTPoison.post(callback_url, Jason.encode!(resultado),
[{"Content-Type", "application/json"}])
end
MinhaApp.Cpf.salvar_resultado(resultado)
end
defp processar_resultado(cpf, _, _callback_url) do
MinhaApp.Cpf.salvar_resultado(%{cpf: cpf, status: "nao_encontrado"})
end
end
Worker de processamento em lote
Para processar múltiplos CPFs, crie um worker que divide o trabalho em jobs individuais:
defmodule MinhaApp.Workers.CpfLoteWorker do
use Oban.Worker,
queue: :cpf_lote,
max_attempts: 1,
unique: [period: 300]
alias MinhaApp.Workers.CpfConsultaWorker
@impl Oban.Worker
def perform(%Oban.Job{args: %{"cpfs" => cpfs, "lote_id" => lote_id}}) do
jobs = Enum.map(cpfs, fn cpf ->
CpfConsultaWorker.new(%{
cpf: cpf,
lote_id: lote_id,
callback_url: nil
})
end)
Oban.insert_all(jobs)
:ok
end
end
Enfileirando jobs
Crie funções de conveniência para enfileirar consultas:
defmodule MinhaApp.Cpf.Enqueuer do
alias MinhaApp.Workers.{CpfConsultaWorker, CpfLoteWorker}
def consultar_async(cpf, opts \\ []) do
callback_url = Keyword.get(opts, :callback_url)
prioridade = Keyword.get(opts, :prioridade, 1)
agendar_em = Keyword.get(opts, :agendar_em)
args = %{cpf: cpf, callback_url: callback_url}
worker_opts = [priority: prioridade]
worker_opts = if agendar_em, do: [{:scheduled_at, agendar_em} | worker_opts], else: worker_opts
args
|> CpfConsultaWorker.new(worker_opts)
|> Oban.insert()
end
def processar_lote(cpfs) do
lote_id = Ecto.UUID.generate()
%{cpfs: cpfs, lote_id: lote_id}
|> CpfLoteWorker.new()
|> Oban.insert()
{:ok, lote_id}
end
def consultar_agendado(cpf, data_hora) do
consultar_async(cpf, agendar_em: data_hora)
end
end
# Uso
MinhaApp.Cpf.Enqueuer.consultar_async("12345678900",
callback_url: "https://meuapp.com/webhook/cpf"
)
MinhaApp.Cpf.Enqueuer.processar_lote([
"12345678900", "98765432100", "11122233344"
])
# Agendar para daqui a 1 hora
MinhaApp.Cpf.Enqueuer.consultar_agendado(
"12345678900",
DateTime.add(DateTime.utc_now(), 3600, :second)
)
Monitorando jobs
Acompanhe o status dos jobs de consulta de CPF:
defmodule MinhaApp.Cpf.Monitor do
import Ecto.Query
alias MinhaApp.Repo
def estatisticas do
query = from j in Oban.Job,
where: j.worker == "MinhaApp.Workers.CpfConsultaWorker",
group_by: j.state,
select: {j.state, count(j.id)}
stats = Repo.all(query) |> Map.new()
%{
pendentes: Map.get(stats, "available", 0),
executando: Map.get(stats, "executing", 0),
concluidos: Map.get(stats, "completed", 0),
com_erro: Map.get(stats, "retryable", 0) + Map.get(stats, "discarded", 0),
agendados: Map.get(stats, "scheduled", 0)
}
end
def jobs_com_erro do
query = from j in Oban.Job,
where: j.worker == "MinhaApp.Workers.CpfConsultaWorker",
where: j.state in ["retryable", "discarded"],
order_by: [desc: j.inserted_at],
limit: 20
Repo.all(query)
end
end
Perguntas frequentes
O que é necessário para implementar consultas de CPF assíncronas com Oban?
Você precisa do Oban configurado com um repo PostgreSQL, de um worker que chama GET https://api.cpfhub.io/cpf/{CPF} com o header x-api-key, e de uma função de enfileiramento. O Oban cuida do retry automático em caso de falha de rede, garantindo que nenhuma consulta se perca.
A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional — portanto, não há necessidade de lógica de back-off por rate limit.
Como definir o número ideal de workers por fila no Oban?
O número de workers (concurrency) deve ser calibrado com base na latência da API (~900ms) e no throughput desejado. Para 5 workers na fila cpf_consultas, você consegue processar aproximadamente 300 consultas por minuto. Monitore a fila com MinhaApp.Cpf.Monitor.estatisticas/0 e ajuste conforme o volume real.
Como garantir conformidade com a LGPD no processamento em background?
Garanta que os dados de CPF nos args do job sejam mascarados nos logs do Oban, defina um max_attempts razoável para evitar reprocessamentos desnecessários, e implemente um período de retenção para jobs concluídos usando Oban.Plugins.Pruner. Documente a finalidade de cada fila para atender ao artigo 37 da LGPD.
Conclusão
O Oban transforma o processamento de consultas de CPF em uma operação confiável e observável. Com filas persistentes, retry automático, agendamento e monitoramento, você garante que nenhuma consulta se perca, mesmo em caso de falhas de rede ou reinicializações da aplicação.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a integrar consultas de CPF em background na sua aplicação Elixir com Oban 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.



