Um campo de CPF offline-first valida o formato localmente sem precisar de internet, armazena resultados anteriores via IndexedDB e enfileira CPFs coletados para validar com a API da CPFHub.io assim que a conexão retorna — garantindo que o fluxo de trabalho nunca seja interrompido por instabilidade de rede.
Introdução
Nem todo usuário tem conexão estável o tempo todo. Representantes comerciais em campo, equipes de atendimento em áreas rurais, eventos presenciais com Wi-Fi instável -- são muitos os cenários onde um formulário com CPF precisa funcionar offline ou com conexão intermitente. Um campo de CPF que trava ou exibe erros quando não há internet frustra o usuário e interrompe o fluxo de trabalho.
Arquitetura offline-first para CPF
A abordagem offline-first significa que a aplicação é projetada para funcionar sem conexão como estado padrão, com a conectividade sendo um recurso adicional. Para validação de CPF, isso implica:
- Validação local de formato: sempre disponível, sem dependência de rede.
- Cache de consultas anteriores: resultados de API armazenados localmente.
- Fila de sincronização: CPFs coletados offline são validados quando a conexão retorna.
- Indicador de estado: o usuário sabe se a validação foi local ou via API.
Detector de conexão
Primeiro, crie um módulo que monitora o estado da conexão:
var ConnectionMonitor = (function () {
var isOnline = navigator.onLine;
var listeners = [];
window.addEventListener("online", function () {
isOnline = true;
notifyListeners();
});
window.addEventListener("offline", function () {
isOnline = false;
notifyListeners();
});
function notifyListeners() {
listeners.forEach(function (fn) {
fn(isOnline);
});
}
function onChange(fn) {
listeners.push(fn);
}
function getStatus() {
return isOnline;
}
return { onChange: onChange, getStatus: getStatus };
})();
Validação local robusta
A validação local é a base da funcionalidade offline. Ela valida o formato sem depender de rede:
var CpfValidator = {
validateFormat: function (cpf) {
var digits = cpf.replace(/\D/g, "");
if (digits.length !== 11) {
return { valid: false, reason: "incomplete" };
}
if (/^(\d)\1{10}$/.test(digits)) {
return { valid: false, reason: "repeated" };
}
// Primeiro dígito verificador
var sum = 0;
for (var i = 0; i < 9; i++) {
sum += parseInt(digits[i]) * (10 - i);
}
var r1 = (sum * 10) % 11;
if (r1 === 10) r1 = 0;
if (r1 !== parseInt(digits[9])) {
return { valid: false, reason: "check_digit_1" };
}
// Segundo dígito verificador
sum = 0;
for (var i = 0; i < 10; i++) {
sum += parseInt(digits[i]) * (11 - i);
}
var r2 = (sum * 10) % 11;
if (r2 === 10) r2 = 0;
if (r2 !== parseInt(digits[10])) {
return { valid: false, reason: "check_digit_2" };
}
return { valid: true, reason: null };
},
};
Cache local com IndexedDB
O IndexedDB oferece armazenamento persistente e estruturado, ideal para cache de consultas de CPF:
var CpfCache = (function () {
var DB_NAME = "cpfhub_cache";
var STORE_NAME = "cpf_results";
var DB_VERSION = 1;
var CACHE_TTL = 24 * 60 * 60 * 1000; // 24 horas
function openDB() {
return new Promise(function (resolve, reject) {
var request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = function (event) {
var db = event.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
var store = db.createObjectStore(STORE_NAME, { keyPath: "cpf" });
store.createIndex("timestamp", "timestamp");
}
};
request.onsuccess = function () {
resolve(request.result);
};
request.onerror = function () {
reject(request.error);
};
});
}
async function get(cpf) {
var db = await openDB();
return new Promise(function (resolve) {
var tx = db.transaction(STORE_NAME, "readonly");
var store = tx.objectStore(STORE_NAME);
var req = store.get(cpf);
req.onsuccess = function () {
var result = req.result;
if (result && Date.now() - result.timestamp < CACHE_TTL) {
resolve(result.data);
} else {
resolve(null);
}
};
req.onerror = function () {
resolve(null);
};
});
}
async function set(cpf, data) {
var db = await openDB();
return new Promise(function (resolve) {
var tx = db.transaction(STORE_NAME, "readwrite");
var store = tx.objectStore(STORE_NAME);
store.put({
cpf: cpf,
data: data,
timestamp: Date.now(),
});
tx.oncomplete = function () {
resolve();
};
});
}
async function cleanup() {
var db = await openDB();
var tx = db.transaction(STORE_NAME, "readwrite");
var store = tx.objectStore(STORE_NAME);
var index = store.index("timestamp");
var cutoff = Date.now() - CACHE_TTL;
var req = index.openCursor(IDBKeyRange.upperBound(cutoff));
req.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
}
return { get: get, set: set, cleanup: cleanup };
})();
Fila de sincronização offline
CPFs coletados offline ficam em uma fila para validação quando a conexão retornar:
var SyncQueue = (function () {
var QUEUE_KEY = "cpfhub_sync_queue";
function getQueue() {
try {
return JSON.parse(localStorage.getItem(QUEUE_KEY) || "[]");
} catch (e) {
return [];
}
}
function saveQueue(queue) {
localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
}
function add(cpf, formData) {
var queue = getQueue();
queue.push({
cpf: cpf,
formData: formData,
timestamp: Date.now(),
status: "pending",
});
saveQueue(queue);
}
async function processQueue() {
var queue = getQueue();
var pending = queue.filter(function (item) {
return item.status === "pending";
});
for (var i = 0; i < pending.length; i++) {
var item = pending[i];
var controller = new AbortController();
var timeoutId = setTimeout(function () {
controller.abort();
}, 10000);
try {
var res = await fetch(
"https://api.cpfhub.io/cpf/" + item.cpf,
{
headers: {
"x-api-key": "SUA_CHAVE_DE_API",
Accept: "application/json",
},
signal: controller.signal,
}
);
clearTimeout(timeoutId);
var data = await res.json();
item.status = data.success ? "validated" : "invalid";
item.apiData = data;
// Cachear resultado
if (data.success) {
await CpfCache.set(item.cpf, data);
}
} catch (err) {
clearTimeout(timeoutId);
// Manter como pendente para nova tentativa
break;
}
}
saveQueue(queue);
return queue;
}
function getPendingCount() {
return getQueue().filter(function (i) {
return i.status === "pending";
}).length;
}
return { add: add, processQueue: processQueue, getPendingCount: getPendingCount };
})();
Componente do campo de CPF offline-first
<div class="cpf-offline" id="cpf-field">
<div class="cpf-offline__status" id="connection-status">
<span class="status-dot"></span>
<span class="status-text"></span>
</div>
<div class="form-group">
<label for="cpf">CPF</label>
<input
type="text"
id="cpf"
class="cpf-input"
inputmode="numeric"
maxlength="14"
placeholder="000.000.000-00"
/>
<div class="cpf-feedback" id="cpf-feedback" role="status" aria-live="polite"></div>
</div>
<div class="sync-info" id="sync-info"></div>
</div>
.cpf-offline__status {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
margin-bottom: 12px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
transition: background-color 0.3s ease;
}
.status-dot--online {
background: #059669;
}
.status-dot--offline {
background: #dc2626;
}
.status-text--online {
color: #059669;
}
.status-text--offline {
color: #dc2626;
}
.cpf-feedback {
font-size: 13px;
margin-top: 6px;
min-height: 20px;
}
.cpf-feedback--local {
color: #f59e0b;
}
.cpf-feedback--api {
color: #059669;
}
.cpf-feedback--cached {
color: #3b82f6;
}
.cpf-feedback--error {
color: #dc2626;
}
.sync-info {
font-size: 12px;
color: #64748b;
margin-top: 8px;
padding: 8px 12px;
background: #f8fafc;
border-radius: 6px;
display: none;
}
.sync-info--visible {
display: block;
}
JavaScript principal
var cpfInput = document.getElementById("cpf");
var feedback = document.getElementById("cpf-feedback");
var statusDot = document.querySelector(".status-dot");
var statusText = document.querySelector(".status-text");
var syncInfo = document.getElementById("sync-info");
var debounceTimer = null;
// Atualizar indicador de conexão
function updateConnectionUI(online) {
statusDot.className = "status-dot status-dot--" + (online ? "online" : "offline");
statusText.className = "status-text status-text--" + (online ? "online" : "offline");
statusText.textContent = online ? "Online" : "Offline";
}
updateConnectionUI(ConnectionMonitor.getStatus());
ConnectionMonitor.onChange(function (online) {
updateConnectionUI(online);
if (online) {
syncPending();
}
});
// Input handler
cpfInput.addEventListener("input", function () {
var v = this.value.replace(/\D/g, "").slice(0, 11);
if (v.length > 9)
v = v.replace(/(\d{3})(\d{3})(\d{3})(\d{1,2})/, "$1.$2.$3-$4");
else if (v.length > 6)
v = v.replace(/(\d{3})(\d{3})(\d{1,3})/, "$1.$2.$3");
else if (v.length > 3) v = v.replace(/(\d{3})(\d{1,3})/, "$1.$2");
this.value = v;
var digits = v.replace(/\D/g, "");
if (digits.length < 11) {
feedback.textContent = "";
return;
}
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
validateCpf(digits);
}, 400);
});
async function validateCpf(cpf) {
// 1. Validação local
var localResult = CpfValidator.validateFormat(cpf);
if (!localResult.valid) {
feedback.textContent = "CPF com formato inválido.";
feedback.className = "cpf-feedback cpf-feedback--error";
return;
}
// 2. Verificar cache
var cached = await CpfCache.get(cpf);
if (cached && cached.success) {
feedback.textContent = "CPF válido (cache) - " + cached.data.name;
feedback.className = "cpf-feedback cpf-feedback--cached";
return;
}
// 3. Se online, consultar API
if (ConnectionMonitor.getStatus()) {
feedback.textContent = "Validando online...";
feedback.className = "cpf-feedback";
var controller = new AbortController();
var timeoutId = setTimeout(function () {
controller.abort();
}, 10000);
try {
var res = await fetch("https://api.cpfhub.io/cpf/" + cpf, {
headers: {
"x-api-key": "SUA_CHAVE_DE_API",
Accept: "application/json",
},
signal: controller.signal,
});
clearTimeout(timeoutId);
var data = await res.json();
if (data.success) {
feedback.textContent = "CPF válido - " + data.data.name;
feedback.className = "cpf-feedback cpf-feedback--api";
await CpfCache.set(cpf, data);
} else {
feedback.textContent = "CPF não encontrado.";
feedback.className = "cpf-feedback cpf-feedback--error";
}
return;
} catch (err) {
clearTimeout(timeoutId);
// Falha de rede -- tratar como offline
}
}
// 4. Modo offline: validação local + fila
feedback.textContent =
"CPF com formato válido (verificação offline). Será validado quando a conexão retornar.";
feedback.className = "cpf-feedback cpf-feedback--local";
SyncQueue.add(cpf, {});
updateSyncInfo();
}
function updateSyncInfo() {
var count = SyncQueue.getPendingCount();
if (count > 0) {
syncInfo.textContent =
count +
(count === 1
? " CPF aguardando validação online."
: " CPFs aguardando validação online.");
syncInfo.className = "sync-info sync-info--visible";
} else {
syncInfo.className = "sync-info";
}
}
async function syncPending() {
var pending = SyncQueue.getPendingCount();
if (pending === 0) return;
syncInfo.textContent = "Sincronizando " + pending + " CPF(s)...";
await SyncQueue.processQueue();
updateSyncInfo();
}
// Limpar cache antigo periodicamente
CpfCache.cleanup();
Service Worker para cache de assets
Para que a página inteira funcione offline, registre um Service Worker:
// sw.js
var CACHE_NAME = "cpf-form-v1";
var ASSETS = [
"/",
"/index.html",
"/css/style.css",
"/js/app.js",
];
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(ASSETS);
})
);
});
self.addEventListener("fetch", function (event) {
// Não cachear chamadas de API
if (event.request.url.includes("api.cpfhub.io")) {
return;
}
event.respondWith(
caches.match(event.request).then(function (response) {
return response || fetch(event.request);
})
);
});
// Registro no HTML principal
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
Perguntas frequentes
O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.
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 — ideal para testes e projetos pequenos. 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.
Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde 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.
Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.
Conclusão
Um campo de CPF offline-first garante que a coleta de dados nunca seja interrompida por problemas de conexão. Com validação local de formato, cache via IndexedDB, fila de sincronização e indicadores claros de estado, o usuário sabe exatamente o que esperar em cada situação. Quando a conexão está disponível, a API do CPFHub.io confirma os dados cadastrais completos — nome, gênero e data de nascimento — em cerca de 900ms.
A API da CPFHub.io oferece respostas em ~900ms, uptime de 99,9% e conformidade com a LGPD. O plano gratuito com 50 consultas mensais é ideal para equipes que trabalham em campo e precisam validar CPFs de forma intermitente.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece 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.



