# Como consumir API de CPF em Tray Commerce via API de integração

> Saiba como consumir a API de CPF da CPFHub em lojas Tray Commerce usando a API de integração para validação no checkout.

**Publicado:** 03/08/2026
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-consumir-api-de-cpf-em-tray-commerce-via-api-de-integracao

---


Para consumir a API de CPF da CPFHub.io em lojas Tray Commerce, crie um proxy server em Node.js que recebe a requisição do tema da loja e consulta `GET https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key`. A Tray não permite chamadas server-side diretas a APIs externas dentro do tema, por isso o proxy protege a chave de API e serve como intermediário. A resposta chega em aproximadamente 900ms e pode ser usada para preencher campos do cadastro automaticamente.

---

## Entendendo a arquitetura da Tray Commerce

A Tray Commerce opera com um sistema de temas baseados em HTML, CSS e JavaScript, com suporte a Liquid (template engine). A plataforma oferece:

- **Temas customizáveis**: acesso ao HTML/CSS/JS do frontend.
- **API REST**: endpoints para gerenciar pedidos, clientes, produtos e mais.
- **Webhooks**: notificações de eventos como criação de pedidos.
- **Scripts customizados**: possibilidade de inserir JavaScript nas páginas.

Para a validação de CPF, a abordagem mais direta é inserir JavaScript customizado no tema que consulta a API da CPFHub através de um proxy server.

---

## Estratégia 1 -- Validação no frontend com proxy

### Por que usar um proxy

A Tray Commerce não permite chamadas server-side diretas a APIs externas dentro do tema. Por isso, precisamos de um servidor intermediário (proxy) que recebe a requisição do frontend, consulta a API da CPFHub e retorna o resultado. Isso também protege a chave de API.

### Criando o proxy com Node.js

```javascript
// proxy-server/index.js
const express = require("express");
const cors = require("cors");
const app = express();

const API_KEY = process.env.CPFHUB_API_KEY;
const ALLOWED_ORIGINS = [
 "https://www.sualoja.com.br",
 "https://sualoja.com.br",
];

app.use(
 cors({
 origin: function (origin, callback) {
 if (!origin || ALLOWED_ORIGINS.includes(origin)) {
 callback(null, true);
 } else {
 callback(new Error("Origem não permitida"));
 }
 },
 })
);

app.use(express.json());

// Rate limiting simples
const requestCounts = new Map();

function rateLimit(req, res, next) {
 const ip = req.ip;
 const now = Date.now();
 const windowMs = 60000;
 const maxRequests = 15;

 if (!requestCounts.has(ip)) {
 requestCounts.set(ip, []);
 }

 const requests = requestCounts
 .get(ip)
 .filter((time) => now - time < windowMs);
 requests.push(now);
 requestCounts.set(ip, requests);

 if (requests.length > maxRequests) {
 return res.status(429).json({
 success: false,
 message: "Muitas requisições. Aguarde um momento.",
 });
 }

 next();
}

app.get("/api/validate-cpf/:cpf", rateLimit, async (req, res) => {
 const cpf = req.params.cpf.replace(/\D/g, "");

 if (cpf.length !== 11) {
 return res.status(400).json({
 success: false,
 message: "CPF deve conter 11 dígitos.",
 });
 }

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

 try {
 const response = await fetch(`https://api.cpfhub.io/cpf/${cpf}`, {
 method: "GET",
 headers: {
 "x-api-key": API_KEY,
 Accept: "application/json",
 },
 signal: controller.signal,
 });

 clearTimeout(timeoutId);
 const data = await response.json();
 res.json(data);
 } catch (error) {
 clearTimeout(timeoutId);
 if (error.name === "AbortError") {
 res.status(504).json({
 success: false,
 message: "Tempo de resposta excedido.",
 });
 } else {
 res.status(500).json({
 success: false,
 message: "Erro ao consultar CPF.",
 });
 }
 }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
 console.log(`Proxy CPFHub rodando na porta ${PORT}`);
});
```

---

## Implementando o JavaScript no tema Tray

### Inserindo o script

Na Tray Commerce, acesse **Aparencia > Editor de Temas > Configurações > Scripts Customizados** e adicione o seguinte código na seção de footer scripts:

```html
<script>
(function() {
 "use strict";

 var PROXY_URL = "https://seu-proxy.herokuapp.com/api/validate-cpf";
 var debounceTimer = null;

 function init() {
 var cpfField = document.querySelector(
 'input[name="cpf"], input[name="CustomerCPF"], #customer_cpf'
 );

 if (!cpfField) return;

 var feedbackEl = document.createElement("div");
 feedbackEl.id = "cpfhub-feedback";
 feedbackEl.style.cssText =
 "font-size:13px;margin-top:4px;min-height:18px;transition:color 0.2s";
 cpfField.parentNode.appendChild(feedbackEl);

 cpfField.addEventListener("input", function() {
 formatCpf(cpfField);

 if (debounceTimer) clearTimeout(debounceTimer);
 debounceTimer = setTimeout(function() {
 var cpf = cpfField.value.replace(/\D/g, "");
 if (cpf.length === 11) {
 validateCpf(cpf, feedbackEl);
 } else {
 feedbackEl.textContent = "";
 feedbackEl.style.color = "";
 }
 }, 600);
 });
 }

 function formatCpf(field) {
 var v = field.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");
 field.value = v;
 }

 function validateCpf(cpf, feedbackEl) {
 feedbackEl.textContent = "Validando CPF...";
 feedbackEl.style.color = "#6b7280";

 var controller = new AbortController();
 var timeoutId = setTimeout(function() { controller.abort(); }, 12000);

 fetch(PROXY_URL + "/" + cpf, {
 method: "GET",
 signal: controller.signal
 })
 .then(function(res) {
 clearTimeout(timeoutId);
 return res.json();
 })
 .then(function(data) {
 if (data.success && data.data) {
 feedbackEl.textContent = "CPF valido - " + data.data.name;
 feedbackEl.style.color = "#059669";
 fillFields(data.data);
 } else {
 feedbackEl.textContent = data.message || "CPF nao encontrado.";
 feedbackEl.style.color = "#dc2626";
 }
 })
 .catch(function(err) {
 clearTimeout(timeoutId);
 feedbackEl.textContent = err.name === "AbortError"
 ? "Tempo excedido."
 : "Erro na validacao.";
 feedbackEl.style.color = "#dc2626";
 });
 }

 function fillFields(data) {
 var nameField = document.querySelector(
 'input[name="name"], input[name="CustomerName"], #customer_name'
 );
 if (nameField && data.name) {
 nameField.value = data.name;
 nameField.dispatchEvent(new Event("change", { bubbles: true }));
 }
 }

 if (document.readyState === "loading") {
 document.addEventListener("DOMContentLoaded", init);
 } else {
 init();
 }
})();
</script>
```

---

## Estratégia 2 -- Integração server-side com webhooks

Para validação no backend -- por exemplo, antes de confirmar um pedido --, use os webhooks da Tray combinados com um servidor externo. Consulte a [documentação de webhooks da Tray](https://developer.tray.com.br/reference/webhooks) para ver todos os eventos disponíveis e os campos retornados no payload.

```javascript
// webhook-handler/index.js
const express = require("express");
const app = express();
app.use(express.json());

const API_KEY = process.env.CPFHUB_API_KEY;
const TRAY_API_URL = process.env.TRAY_API_URL;
const TRAY_ACCESS_TOKEN = process.env.TRAY_ACCESS_TOKEN;

app.post("/webhooks/tray/order-created", async (req, res) => {
 const { order_id } = req.body;

 try {
 // Buscar dados do pedido na API Tray
 const orderResponse = await fetch(
 `${TRAY_API_URL}/orders/${order_id}?access_token=${TRAY_ACCESS_TOKEN}`,
 { signal: AbortSignal.timeout(10000) }
 );
 const orderData = await orderResponse.json();
 const cpf = orderData.Order.Customer.cpf.replace(/\D/g, "");

 // Validar CPF na API CPFHub
 const cpfController = new AbortController();
 const cpfTimeout = setTimeout(() => cpfController.abort(), 10000);

 const cpfResponse = await fetch(
 `https://api.cpfhub.io/cpf/${cpf}`,
 {
 headers: {
 "x-api-key": API_KEY,
 Accept: "application/json",
 },
 signal: cpfController.signal,
 }
 );

 clearTimeout(cpfTimeout);
 const cpfData = await cpfResponse.json();

 if (!cpfData.success) {
 // Marcar pedido para revisão manual
 await fetch(
 `${TRAY_API_URL}/orders/${order_id}?access_token=${TRAY_ACCESS_TOKEN}`,
 {
 method: "PUT",
 headers: { "Content-Type": "application/json" },
 body: JSON.stringify({
 Order: {
 status: "REVIEW",
 observation: "CPF nao validado pela API CPFHub",
 },
 }),
 signal: AbortSignal.timeout(10000),
 }
 );
 }

 res.status(200).json({ received: true });
 } catch (error) {
 console.error("Erro no webhook:", error.message);
 res.status(500).json({ error: "Erro ao processar webhook" });
 }
});

app.listen(3001);
```

---

## Configurando webhooks na Tray

Registre o webhook via API da Tray:

```bash
curl -X POST "https://api.traycorp.com.br/webhooks" \
 -H "Content-Type: application/json" \
 -d '{
 "Webhook": {
 "url": "https://seu-servidor.com/webhooks/tray/order-created",
 "event": "order_created",
 "access_token": "SEU_TOKEN_TRAY"
 }
 }'
```

---

## Deploy do proxy

Para colocar o proxy em produção, você pode usar serviços como Railway, Render ou Fly.io:

```bash
# Exemplo com Railway
npm init -y
npm install express cors

# Criar Procfile
echo "web: node index.js" > Procfile

# Deploy
railway up
```

Defina a variável de ambiente `CPFHUB_API_KEY` no painel do serviço de hospedagem.

---

## Testando a integração

Após configurar o proxy e inserir o script no tema:

1. Acesse a página de cadastro da sua loja Tray.
2. Digite um CPF no campo correspondente.
3. Aguarde o feedback visual de validação.
4. Verifique se os campos de nome são preenchidos automaticamente.

---

## Perguntas frequentes

### Por que preciso de um proxy para validar CPF em uma loja Tray Commerce?

A Tray Commerce não permite que o tema faça chamadas server-side diretas a APIs externas, e expor a chave de API no JavaScript do frontend seria um risco de segurança. O proxy recebe a requisição do browser da loja, adiciona o header `x-api-key` no servidor e repassa a consulta à CPFHub.io. Isso mantém a chave protegida e o fluxo funcional.

### Qual é a latência esperada ao validar CPF via proxy na Tray?

A API CPFHub.io responde em aproximadamente 900ms. Somado ao tempo do proxy, o ciclo completo fica em torno de 1-1,5 segundo. Configure o debounce no script do tema (o exemplo usa 600ms) para que a consulta só seja disparada quando o usuário terminar de digitar, evitando requisições desnecessárias.

### O que acontece se o limite de consultas do plano gratuito for atingido?

O plano gratuito cobre 50 consultas por mês. Ao atingir esse limite, a API não bloqueia: cada consulta adicional é cobrada a R$0,15. Para lojas com volume maior, o plano Pro inclui 1.000 consultas mensais por R$149, com o mesmo custo por excedente.

### Como lidar com CPFs inválidos ou pedidos suspeitos na integração com webhooks?

Quando o webhook recebe um pedido e a consulta à CPFHub.io retorna `success: false`, a recomendação é marcar o pedido com status de revisão manual em vez de cancelá-lo automaticamente. Isso preserva pedidos legítimos com CPFs temporariamente indisponíveis na base de dados e cria um fluxo de análise controlado pela equipe de operações.

### Leia também

- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [Boas práticas para consumir APIs de CPF de forma segura](https://cpfhub.io/blog/boas-praticas-consumir-apis-cpf-segura)
- [Como integrar validação de CPF em VTEX IO com service worker](https://cpfhub.io/blog/como-integrar-validacao-de-cpf-em-vtex-io-com-service-worker)
- [Como evitar chargebacks usando validação de CPF no checkout](https://cpfhub.io/blog/como-evitar-chargebacks-usando-validacao-de-cpf-no-checkout)

---

## Conclusão

A integração de validação de CPF em lojas Tray Commerce com a API do [**CPFHub.io**](https://www.cpfhub.io/)

A API da CPFHub entrega respostas em cerca de 900ms, com uptime de 99,9% e conformidade total com a LGPD -- essencial para lojas que operam no mercado brasileiro.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/)

