Como validar CPF em tempo real usando Elixir e APIs REST

Aprenda a validar CPF em tempo real em Elixir combinando validação algorítmica com consulta à API REST do CPFHub.

Redação CPFHub.io
Redação CPFHub.io
··6 min de leitura
Como validar CPF em tempo real usando Elixir e APIs REST

Validar CPF em tempo real com Elixir combina dois passos: verificação algorítmica dos dígitos verificadores (< 0,1ms, sem rede) e consulta à API REST do CPFHub.io para confirmar os dados cadastrais (~900ms). O modelo de processos leves da BEAM VM permite executar centenas dessas validações em paralelo sem degradar a performance da aplicação.

Introdução

A validação de CPF em tempo real é crucial para garantir a qualidade dos dados em formulários de cadastro. Elixir, com seu modelo de concorrência baseado em processos leves, é ideal para esse tipo de operação que combina processamento local rápido com chamadas assíncronas a APIs externas.


Validação algorítmica do CPF

Implemente a verificação dos dígitos verificadores de forma funcional e idiomática em Elixir:

defmodule CpfValidator do
    @moduledoc """
    Módulo para validação algorítmica de CPF.
    """

    @doc """
    Valida os dígitos verificadores de um CPF.
    """
    def validar(cpf) when is_binary(cpf) do
    cpf
    |> String.replace(~r/\D/, "")
    |> validar_digitos()
    end

    defp validar_digitos(cpf) when byte_size(cpf) != 11, do: {:error, :tamanho_invalido}

    defp validar_digitos(cpf) do
    digitos = cpf |> String.graphemes() |> Enum.map(&String.to_integer/1)

    if Enum.uniq(digitos) |> length() == 1 do
    {:error, :digitos_repetidos}
    else
    d1 = calcular_digito(digitos, 9, 10)
    d2 = calcular_digito(digitos, 10, 11)

    if Enum.at(digitos, 9) == d1 and Enum.at(digitos, 10) == d2 do
    :ok
    else
    {:error, :digitos_verificadores_invalidos}
    end
    end
    end

    defp calcular_digito(digitos, count, peso_inicial) do
    soma =
    digitos
    |> Enum.take(count)
    |> Enum.with_index()
    |> Enum.reduce(0, fn {d, i}, acc -> acc + d * (peso_inicial - i) end)

    resto = rem(soma, 11)
    if resto < 2, do: 0, else: 11 - resto
    end
end

Serviço de validação em tempo real

Combine a validação local com a consulta à API para fornecer um resultado completo:

defmodule CpfValidationService do
    @moduledoc """
    Serviço de validação de CPF em tempo real com consulta à API.
    """

    alias CpfValidator

    @base_url "https://api.cpfhub.io"

    def validar_completo(cpf, api_key) do
    cpf_limpo = String.replace(cpf, ~r/\D/, "")

    with :ok <- CpfValidator.validar(cpf_limpo),
    {:ok, dados} <- consultar_api(cpf_limpo, api_key) do
    {:ok, %{
    formato_valido: true,
    encontrado: true,
    nome: dados["name"],
    data_nascimento: dados["birthDate"],
    genero: dados["gender"],
    mensagem: "CPF válido e encontrado na base"
    }}
    else
    {:error, :tamanho_invalido} ->
    {:error, %{formato_valido: false, mensagem: "CPF deve conter 11 dígitos"}}

    {:error, :digitos_repetidos} ->
    {:error, %{formato_valido: false, mensagem: "CPF com dígitos repetidos"}}

    {:error, :digitos_verificadores_invalidos} ->
    {:error, %{formato_valido: false, mensagem: "Dígitos verificadores inválidos"}}

    {:error, :cpf_nao_encontrado} ->
    {:ok, %{
    formato_valido: true,
    encontrado: false,
    nome: nil,
    data_nascimento: nil,
    genero: nil,
    mensagem: "CPF válido, porém não encontrado na base"
    }}

    {:error, reason} ->
    {:error, %{formato_valido: true, mensagem: "Erro: #{inspect(reason)}"}}
    end
    end

    defp consultar_api(cpf, api_key) do
    headers = [{"x-api-key", api_key}]
    url = "#{@base_url}/cpf/#{cpf}"

    case HTTPoison.get(url, headers, recv_timeout: 5000) do
    {:ok, %{status_code: 200, body: body}} ->
    case Jason.decode!(body) do
    %{"success" => true, "data" => data} -> {:ok, data}
    _ -> {:error, :cpf_nao_encontrado}
    end

    {:ok, %{status_code: 404}} ->
    {:error, :cpf_nao_encontrado}

    {:error, reason} ->
    {:error, {:falha_api, reason}}
    end
    end
end
EtapaTempoRecurso utilizado
Limpeza do CPF< 0.1msRegex local
Validação de dígitos< 0.1msCálculo aritmético
Consulta à API~900msHTTP via rede
Resposta total~900msResultado combinado

Integrando com LiveView para feedback instantâneo

O Phoenix LiveView permite validação em tempo real com feedback visual imediato:

defmodule MinhaAppWeb.CpfValidationLive do
    use MinhaAppWeb, :live_view

    def mount(_params, _session, socket) do
    {:ok, assign(socket,
    cpf_input: "",
    validacao_local: nil,
    resultado_api: nil,
    loading: false
    )}
    end

    def handle_event("validar_local", %{"cpf" => cpf}, socket) do
    resultado = CpfValidator.validar(cpf)
    {:noreply, assign(socket, cpf_input: cpf, validacao_local: resultado)}
    end

    def handle_event("consultar_api", %{"cpf" => cpf}, socket) do
    socket = assign(socket, loading: true)
    api_key = Application.get_env(:minha_app, :cpfhub)[:api_key]

    case CpfValidationService.validar_completo(cpf, api_key) do
    {:ok, resultado} ->
    {:noreply, assign(socket, resultado_api: resultado, loading: false)}

    {:error, resultado} ->
    {:noreply, assign(socket, resultado_api: resultado, loading: false)}
    end
    end

    def render(assigns) do
    ~H"""
    <div class="max-w-md mx-auto">
    <h2>Validação de CPF</h2>

    <form phx-change="validar_local" phx-submit="consultar_api">
    <input type="text" name="cpf" value={@cpf_input}
    placeholder="000.000.000-00" maxlength="14"
    class="form-input w-full" />

    <button type="submit" disabled={@loading} class="btn mt-2">
    <%= if @loading, do: "Consultando...", else: "Consultar" %>
    </button>
    </form>

    <%= if @resultado_api do %>
    <div class="mt-4 p-4 border rounded">
    <p><strong>Status:</strong> <%= @resultado_api.mensagem %></p>
    <%= if @resultado_api[:nome] do %>
    <p><strong>Nome:</strong> <%= @resultado_api.nome %></p>
    <p><strong>Nascimento:</strong> <%= @resultado_api.data_nascimento %></p>
    <% end %>
    </div>
    <% end %>
    </div>
    """
    end
end

Adicionando cache com ETS

Para evitar consultas repetidas, utilize ETS como cache em memória:

defmodule CpfCache do
    use GenServer

    @table_name :cpf_cache
    @ttl_seconds 600

    def start_link(_opts) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
    end

    def init(_) do
    :ets.new(@table_name, [:named_table, :public, read_concurrency: true])
    {:ok, %{}}
    end

    def get(cpf) do
    case :ets.lookup(@table_name, cpf) do
    [{^cpf, resultado, timestamp}] ->
    if System.system_time(:second) - timestamp < @ttl_seconds do
    {:ok, resultado}
    else
    :ets.delete(@table_name, cpf)
    :miss
    end
    [] -> :miss
    end
    end

    def put(cpf, resultado) do
    :ets.insert(@table_name, {cpf, resultado, System.system_time(:second)})
    :ok
    end
end

Perguntas frequentes

Por que separar a validação algorítmica da consulta à API em Elixir?

A validação algorítmica (dígitos verificadores) é local e executa em menos de 0,1ms — ela elimina CPFs estruturalmente inválidos antes de qualquer chamada de rede. Isso evita consumo desnecessário de quota da API e reduz a latência para entradas inválidas. Apenas CPFs que passam na validação local disparam a consulta HTTP ao CPFHub.io.

Como processar múltiplas validações de CPF em paralelo com Elixir?

Use Task.async_stream/3 para paralelizar as consultas dentro do limite de concorrência desejado. A BEAM VM agenda os processos leves de forma eficiente, e cada consulta à API (~900ms) é executada de forma independente. Para cenários de alta escala, considere Oban para gerenciar filas de validação em background.

A API CPFHub.io retorna HTTP 429 quando o limite é atingido?

Não. A CPFHub.io não bloqueia e não retorna erro 429: consultas excedentes são cobradas a R$ 0,15 cada. O plano gratuito oferece 50 consultas mensais sem cartão de crédito; o plano Pro inclui 1.000 consultas por R$ 149/mês. Isso simplifica o tratamento de erros no código Elixir — não é necessário lidar com backoff por limite de taxa.

Como garantir conformidade com a LGPD ao usar a API de CPF em aplicações Elixir?

Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não persista o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A ANPD orienta que dados de identificação devem ser tratados com o princípio da necessidade.


Conclusão

A validação de CPF em tempo real com Elixir combina a eficiência da BEAM VM com a riqueza de dados da API. O modelo de concorrência do Elixir permite processar múltiplas validações simultaneamente sem degradação de performance, e o cache com ETS elimina consultas redundantes.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e integre a validação de CPF na sua aplicação Elixir com dados cadastrais reais em produção.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

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.

WhatsAppFale conosco via WhatsApp