Como consumir API de CPF em Firebase Functions para backend serverless

Aprenda a criar Firebase Functions que consultam CPF via API CPFHub.io com TypeScript, Firestore para cache e integração com Firebase Auth.

Lucas Vieira
Lucas Vieira
··8 min de leitura
Como consumir API de CPF em Firebase Functions para backend serverless

Para integrar a API de CPF da CPFHub.io em um backend serverless com Firebase, você cria uma Firebase Function que faz uma chamada GET autenticada para https://api.cpfhub.io/cpf/{CPF} e armazena o resultado no Firestore como cache. A latência típica da API é de ~150 ms, por isso o cache no Firestore é indispensável para manter o tempo de resposta aceitável em produção. O deploy na região southamerica-east1 garante a menor latência possível para usuários brasileiros.

Firebase e a plataforma de desenvolvimento de aplicações do Google que oferece um ecossistema completo: autenticação, banco de dados em tempo real, hosting, storage e Cloud Functions. As Firebase Functions (baseadas no Google Cloud Functions) permitem executar código backend em resposta a eventos HTTP, alteracoes no Firestore ou gatilhos de autenticação -- tudo sem gerenciar servidores.

Para aplicações que utilizam Firebase como backend -- apps mobile, SPAs e plataformas web -- as Firebase Functions são a forma natural de integrar a API da CPFHub.io

Configuração do projeto

Inicialize o projeto Firebase com Functions:

# Instalar Firebase CLI
npm install -g firebase-tools

# Login
firebase login

# Inicializar Functions
firebase init functions

# Selecione:
# - TypeScript
# - ESLint: Sim
# - Instalar dependencias: Sim

Estrutura resultante:

functions/
    src/
    index.ts
    package.json
    tsconfig.json

Serviço de consulta de CPF

Crie o módulo que encapsula a comunicação com a API:

// functions/src/services/cpfhub.ts
import { defineString } from "firebase-functions/params";

const cpfhubApiKey = defineString("CPFHUB_API_KEY");

const API_URL = "https://api.cpfhub.io/cpf";
const TIMEOUT_MS = 10000;

interface CPFData {
    cpf: string;
    name: string;
    nameUpper: string;
    gender: string;
    birthDate: string;
    day: number;
    month: number;
    year: number;
}

interface CPFApiResponse {
    success: boolean;
    data?: CPFData;
}

export interface ConsultaResultado {
    sucesso: boolean;
    dados?: {
    cpf: string;
    nome: string;
    genero: string;
    dataNascimento: string;
    dia: number;
    mes: number;
    ano: number;
    };
    erro?: string;
}

export async function consultarCPF(cpf: string): Promise<ConsultaResultado> {
    const cpfLimpo = cpf.replace(/\D/g, "");

    if (cpfLimpo.length !== 11) {
    return { sucesso: false, erro: "CPF deve conter 11 digitos" };
    }

    if (/^(\d)\1+$/.test(cpfLimpo)) {
    return { sucesso: false, erro: "CPF invalido" };
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);

    try {
    const response = await fetch(`${API_URL}/${cpfLimpo}`, {
    method: "GET",
    headers: {
    "x-api-key": cpfhubApiKey.value(),
    "Accept": "application/json",
    },
    signal: controller.signal,
    });

    clearTimeout(timeoutId);

    if (!response.ok) {
    return { sucesso: false, erro: `Erro HTTP ${response.status}` };
    }

    const resultado: CPFApiResponse = await response.json();

    if (!resultado.success || !resultado.data) {
    return { sucesso: false, erro: "CPF nao encontrado" };
    }

    return {
    sucesso: true,
    dados: {
    cpf: resultado.data.cpf,
    nome: resultado.data.name,
    genero: resultado.data.gender,
    dataNascimento: resultado.data.birthDate,
    dia: resultado.data.day,
    mes: resultado.data.month,
    ano: resultado.data.year,
    },
    };
    } catch (error) {
    clearTimeout(timeoutId);

    if (error instanceof Error && error.name === "AbortError") {
    return { sucesso: false, erro: "Timeout na consulta" };
    }

    return { sucesso: false, erro: `Erro: ${String(error)}` };
    }
}

O defineString do Firebase Functions v2 permite definir parametros de configuração que são solicitados no deploy.


Função HTTP para validação

A função HTTP e o ponto de entrada para consultas via API REST:

// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { getFirestore, FieldValue } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";
import { consultarCPF } from "./services/cpfhub";

initializeApp();
const db = getFirestore();

const CACHE_COLLECTION = "cpf_cache";
const CACHE_TTL_HOURS = 24;

export const validarCpf = onRequest(
    {
    region: "southamerica-east1",
    cors: true,
    maxInstances: 10,
    },
    async (req, res) => {
    if (req.method !== "GET") {
    res.status(405).json({ erro: "Use GET" });
    return;
    }

    const cpf = req.query.cpf as string || req.params[0];

    if (!cpf) {
    res.status(400).json({ erro: "CPF e obrigatorio" });
    return;
    }

    const cpfLimpo = cpf.replace(/\D/g, "");

    // Verificar cache no Firestore
    const cacheDoc = await db
    .collection(CACHE_COLLECTION)
    .doc(cpfLimpo)
    .get();

    if (cacheDoc.exists) {
    const cacheData = cacheDoc.data();
    const expiraEm = cacheData?.expiraEm?.toDate();

    if (expiraEm && expiraEm > new Date()) {
    res.json({
    valido: true,
    dados: cacheData?.dados,
    fonte: "cache",
    });
    return;
    }
    }

    // Consultar API
    const resultado = await consultarCPF(cpfLimpo);

    if (!resultado.sucesso) {
    const status = resultado.erro?.includes("nao encontrado") ? 404 : 500;
    res.status(status).json({ erro: resultado.erro });
    return;
    }

    // Salvar no cache
    const expiraEm = new Date();
    expiraEm.setHours(expiraEm.getHours() + CACHE_TTL_HOURS);

    await db.collection(CACHE_COLLECTION).doc(cpfLimpo).set({
    dados: resultado.dados,
    expiraEm,
    criadoEm: FieldValue.serverTimestamp(),
    });

    res.json({
    valido: true,
    dados: resultado.dados,
    fonte: "api",
    });
    }
);

Função acionada por evento do Firestore

Valide CPF automaticamente quando um novo documento e criado no Firestore:

// functions/src/index.ts (continuacao)

export const validarCpfNoCadastro = onDocumentCreated(
    {
    document: "cadastros/{docId}",
    region: "southamerica-east1",
    },
    async (event) => {
    const snapshot = event.data;

    if (!snapshot) {
    console.log("Documento vazio");
    return;
    }

    const dados = snapshot.data();
    const cpf = dados.cpf;

    if (!cpf) {
    console.log("CPF nao informado no documento");
    return;
    }

    const resultado = await consultarCPF(cpf);

    const atualizacao: Record<string, unknown> = {
    cpfValidadoEm: FieldValue.serverTimestamp(),
    };

    if (resultado.sucesso) {
    atualizacao.cpfStatus = "validado";
    atualizacao.cpfNomeValidado = resultado.dados?.nome;
    atualizacao.cpfGenero = resultado.dados?.genero;
    atualizacao.cpfDataNascimento = resultado.dados?.dataNascimento;

    // Verificar se o nome confere
    const nomeInformado = (dados.nome || "").toUpperCase().split(" ")[0];
    const nomeAPI = (resultado.dados?.nome || "").toUpperCase().split(" ")[0];

    if (nomeInformado && nomeAPI && nomeInformado !== nomeAPI) {
    atualizacao.cpfStatus = "divergente";
    atualizacao.cpfAlerta = "Nome informado diverge do CPF";
    }
    } else {
    atualizacao.cpfStatus = "invalido";
    atualizacao.cpfErro = resultado.erro;
    }

    await snapshot.ref.update(atualizacao);
    console.log(`CPF ${cpf} validado: ${atualizacao.cpfStatus}`);
    }
);

Com esse trigger, qualquer documento adicionado a coleção cadastros no Firestore tera o CPF validado automaticamente.


Callable Function para apps Flutter e React Native

Firebase Callable Functions são a forma recomendada de chamar funções a partir de apps mobile:

// functions/src/index.ts (continuacao)
import { onCall, HttpsError } from "firebase-functions/v2/https";

export const consultarCpfCallable = onCall(
    {
    region: "southamerica-east1",
    maxInstances: 10,
    },
    async (request) => {
    // Verificar autenticacao
    if (!request.auth) {
    throw new HttpsError(
    "unauthenticated",
    "Usuario nao autenticado"
    );
    }

    const cpf = request.data.cpf;

    if (!cpf) {
    throw new HttpsError(
    "invalid-argument",
    "CPF e obrigatorio"
    );
    }

    const resultado = await consultarCPF(cpf);

    if (!resultado.sucesso) {
    throw new HttpsError(
    "not-found",
    resultado.erro ?? "CPF nao encontrado"
    );
    }

    return resultado.dados;
    }
);

No app mobile (JavaScript/React Native):

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions(app, "southamerica-east1");
const consultarCPF = httpsCallable(functions, "consultarCpfCallable");

async function validar(cpf: string) {
    try {
    const result = await consultarCPF({ cpf });
    console.log("Dados:", result.data);
    } catch (error) {
    console.error("Erro:", error);
    }
}

Configuração de segredos

Configure a chave de API como parametro do Firebase:

# Definir parametro
firebase functions:secrets:set CPFHUB_API_KEY

# Verificar
firebase functions:secrets:access CPFHUB_API_KEY

Deploy e teste

# Deploy de todas as funcoes
firebase deploy --only functions

# Deploy de funcao especifica
firebase deploy --only functions:validarCpf

# Testar localmente
firebase emulators:start --only functions,firestore

Teste local com curl:

curl "http://localhost:5001/meu-projeto/southamerica-east1/validarCpf?cpf=12345678901"

Limpeza de cache com Scheduled Function

Crie uma função agendada para limpar cache expirado:

import { onSchedule } from "firebase-functions/v2/scheduler";

export const limparCacheCPF = onSchedule(
    {
    schedule: "every 24 hours",
    region: "southamerica-east1",
    },
    async () => {
    const agora = new Date();
    const snapshot = await db
    .collection(CACHE_COLLECTION)
    .where("expiraEm", "<", agora)
    .limit(500)
    .get();

    const batch = db.batch();
    snapshot.docs.forEach((doc) => batch.delete(doc.ref));
    await batch.commit();

    console.log(`${snapshot.size} registros de cache removidos`);
    }
);

Perguntas frequentes

Por que usar o Firestore como cache para chamadas à API de CPF em Firebase Functions?

A API de CPF tem latência de ~150 ms por consulta. Sem cache, cada validação bloqueia a Function pelo tempo de ida e volta da requisição, aumentando o custo de execução (Firebase Functions cobra por tempo de CPU) e prejudicando a experiência do usuário. O Firestore como cache reduz chamadas repetidas ao mesmo CPF e mantém os dados disponíveis entre cold starts de Function.

O que acontece se o limite do plano gratuito for atingido durante a execução das Functions?

A cota mensal e o limite por minuto não se confundem. No plano Grátis, zerar os 50 créditos pausa as consultas com HTTP 403 ("Limite de créditos excedido"); nos planos pagos, o excedente eventual é faturado depois, no preço da página de preços. Acima do limite por minuto a API responde HTTP 429 e informa o Retry-After. Para ambientes de produção com volume previsível, o plano Pro (R$149/mês, 1.000 consultas) é mais adequado.

Como proteger a chave de API da CPFHub.io em Firebase Functions?

Use firebase functions:secrets:set CPFHUB_API_KEY para armazenar a chave no Secret Manager do Google Cloud, que é o mecanismo nativo do Firebase Functions v2 para segredos. Nunca hardcode a chave no código-fonte nem a coloque em variáveis de ambiente não criptografadas. Consulte a documentação oficial do Firebase para boas práticas de configuração.

Como lidar com timeouts e falhas de rede nas chamadas à API dentro das Functions?

Configure AbortController com timeout de 10 segundos (como no exemplo acima) e implemente retry com backoff exponencial para falhas transitórias. O OWASP recomenda que integrações com APIs externas sempre tenham timeout definido e tratamento explícito de erros de rede para evitar que a Function fique pendurada até o limite máximo de execução.


Conclusão

Firebase Functions oferecem uma integração natural com o ecossistema Firebase para validação de CPF. Funções HTTP para APIs REST, triggers do Firestore para validação automática e Callable Functions para apps mobile criam uma solução completa. O Firestore serve como cache distribuido, e a regiao southamerica-east1 garante a menor latência para usuários brasileiros. A API da CPFHub.io

Cadastre-se em cpfhub.io

CPFHub.io

Pronto para integrar a API?

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

WhatsAppFale conosco via WhatsApp