Cloudflare — Workers, Pages e KV¶
O Cloudflare é a camada de entrada e entrega do ecossistema Veggi. Recebe os eventos externos (e-mails com NF-e e formulários do site), serve o portal do cliente e gerencia o cache de tokens. Os Workers e Pages não têm banco de dados próprio — dependem do KV para cache temporário e do n8n/PostgreSQL para persistência.
Componentes¶
| Componente | Nome | Domínio | Papel |
|---|---|---|---|
| Worker | nfe |
nfe@nfe.grupoveggi.com.br |
Recebe e-mails com NF-e e repassa XML para o n8n |
| Worker | drive |
drive.grupoveggi.com.br |
Serve portal de download e API de token |
| Worker | rd-leads |
api.grupoveggi.com.br |
Backend do site institucional — recebe leads e envia ao RD Station |
| Pages | connect-hub |
hub.grupoveggi.com.br |
Portal React onde o cliente visualiza e seleciona as fotos |
| Pages | veggi-style-catalogue |
institucional.grupoveggi.com.br |
Site institucional com formulários de captação |
| Worker | veggi-validador-email |
veggi-validador-email.veggiageral.workers.dev |
Valida e-mail no checkout da loja VTEX useveggi.com.br |
| KV Namespace | DOWNLOAD_STORAGE |
— | Cache de tokens de acesso (TTL 45 dias) |
| KV Namespace | CACHE |
— | Cache de DNS por domínio (TTL 7 dias) + chave blocklist |
| Worker | veggi-meta-updater |
veggi-meta-updater.veggiageral.workers.dev |
Robô que busca Meta, CRM, VTEX e Google Ads (Módulo 6) |
| Worker | veggi-mcp |
veggi-mcp.veggiageral.workers.dev |
Consultor de Tráfego via MCP (Módulo 7) |
| Worker | veggi-enriquecedor |
veggi-enriquecedor.veggiageral.workers.dev |
Enriquece negociações do RD CRM pela Receita (Módulo 8) |
| Worker | veggi-atribuicao |
veggi-atribuicao.veggiageral.workers.dev |
Impede negociação nova de herdar UTM antiga do contato (Rastreamento) |
| Pages | veggi-meta |
dashmeta.grupoveggi.com.br |
Dashboard de anúncios — protegida por Cloudflare Access |
| KV Namespace | VEGGI_DATA |
— | latest, crm_history e consultor_memory |
| KV Namespace | ENRIQUECEDOR |
— | Catálogo de empresas do enriquecedor |
Como os componentes se encaixam no fluxo completo¶
Cliente (e-mail com NF-e)
│
▼
Worker nfe ── parseia MIME, extrai XML ──► n8n Webhook
│
(processa NF-e,
gera token, salva
PostgreSQL + KV)
│
KV DOWNLOAD_STORAGE
│
┌─────────────────────┤
│ │
hub.grupoveggi.com.br drive.grupoveggi.com.br
(Pages: connect-hub) (Worker: drive)
│ │
Cliente visualiza Cliente baixa ZIP
e seleciona fotos (gerado no browser via
client-zip + Backblaze)
Formulário no site institucional
│
▼
Worker rd-leads ── valida Turnstile + CORS ──► RD Station Marketing
│
▼
Automação → RD CRM
Worker: nfe¶
Trigger: E-mail recebido em nfe@nfe.grupoveggi.com.br (zona grupoveggi.com.br)
Domínio workers.dev: nfe.veggiageral.workers.dev
Variável secreta: NFE_WEBHOOK_URL — URL do webhook n8n
Atua como um roteador de e-mail para webhook. Toda NF-e enviada por e-mail é processada pelo Worker: ele parseia o MIME, extrai os anexos XML e encaminha um payload JSON compatível para o webhook do n8n — sem precisar de nenhum serviço intermediário como o CloudMailin.
O que o Worker faz (passo a passo)¶
- Recebe o e-mail bruto (RFC822) via handler
email() - Parseia o MIME completo — separa headers, corpo e partes
- Filtra apenas anexos com content-type XML ou extensão
.xml - Decodifica o corpo do anexo (suporta
base64,quoted-printable,7bit/8bit) - Monta um payload JSON no formato compatível com o n8n
- Faz
POSTpara o webhook do n8n com o payload - Retorna sem rejeitar o e-mail — erros são logados mas não bloqueiam o remetente
Funções auxiliares internas¶
| Função | O que faz |
|---|---|
parseMime(raw) |
Parseia o e-mail completo — detecta multipart, extrai partes |
parseHeaders(text) |
Parseia headers HTTP/MIME em objeto chave-valor |
extractBoundary(ct) |
Extrai o boundary do Content-Type multipart |
extractFilename(cd) |
Extrai o nome do arquivo do Content-Disposition |
decodeBody(body, enc) |
Decodifica o corpo: base64, quoted-printable ou passthrough |
decodeQuotedPrintable(input) |
Decodifica encoding quoted-printable |
Estrutura do Payload enviado ao n8n¶
{
"envelope": {
"to": "nfe@nfe.grupoveggi.com.br",
"from": "remetente@erp.com"
},
"headers": { "...": "..." },
"attachments": [
{
"file_name": "Nfe123.xml",
"filename": "Nfe123.xml",
"content_type": "application/xml",
"content": "<nfeProc>...</nfeProc>",
"size": 60590,
"disposition": "attachment"
}
],
"body": { "attachments": [...] },
"rawSize": 564425
}
Info
O campo body.attachments é mantido por compatibilidade com nós legados do n8n. Ambos attachments (raiz) e body.attachments contêm os mesmos dados.
Warning
O Worker nunca chama setReject(). Mesmo em caso de erro, o e-mail é aceito e o erro é apenas logado. Isso evita que o sistema de e-mail do ERP receba bounces e pare de enviar NF-es.
Worker: drive¶
Domínio: drive.grupoveggi.com.br · workers.dev: drive.veggiageral.workers.dev
Variáveis: DRIVE_SECRET = veggi-drive-2026-xpto · KV binding: DOWNLOAD_STORAGE
Worker central do portal do cliente. Serve 5 funções distintas dependendo da rota acessada:
Endereço base
https://drive.grupoveggi.com.br
As rotas abaixo são o caminho depois desse endereço.
| Rota | Método | Função |
|---|---|---|
/api/token/{token} |
GET | API JSON — retorna dados do pedido pelo token (usada pelo Pages) |
/file-proxy?path=... |
GET | Proxy reverso para arquivos no Backblaze B2 (cache 1h) |
/entrega |
POST | Recebe seleção de arquivos, gera token temporário de download |
/{token} |
GET | Página HTML com botão para baixar ZIP |
/{token}?download=1 |
GET | Executa download do ZIP inteiramente no navegador (client-zip) |
/ |
GET | Página de fallback com instruções |
Rota POST /entrega¶
- Valida o header
X-Drive-Secretcontra a variávelDRIVE_SECRET - Lê o token principal do KV
- Gera um token temporário de download:
{token}-{uuid12chars} - Salva no KV com TTL de 30 minutos apenas os arquivos selecionados
- Retorna a
download_urlcom o token temporário
Info
O token original nunca é sobrescrito. O token temporário existe apenas para o download pontual e expira em 30 min.
Rota GET /{token}?download=1¶
Página HTML que executa o download do ZIP inteiramente no navegador do cliente (sem processamento no servidor):
- Busca a lista de arquivos do token no KV
- Baixa cada arquivo via
/file-proxycom retry automático (3 tentativas) - Usa a biblioteca
client-zip(ESM viaesm.sh) para montar o ZIP no navegador - Oferece o download automático do arquivo
Pedido_{numeroNF}.zip - Exibe barra de progresso, contagem de OK/falhas e log de erros
Warning
Mover DRIVE_SECRET de "Texto não criptografado" para "Secreto" no painel do Cloudflare.
Worker: rd-leads¶
Domínio: api.grupoveggi.com.br · Nome: rd-leads
Arquivo principal: worker.js (código único, sem dependências externas)
Backend do site institucional. Recebe os dados dos formulários de captação, valida o captcha Turnstile e envia eventos de conversão para o RD Station Marketing.
Variáveis de ambiente (Secrets)¶
| Variável | Tipo | Descrição |
|---|---|---|
ALLOWED_ORIGIN |
Secreto | Domínio autorizado a chamar o Worker |
TURNSTILE_SECRET |
Secreto | Chave secreta do Turnstile para validação server-side |
RD_CLIENT_ID |
Secreto | Client ID do app OAuth no RD Station |
RD_CLIENT_SECRET |
Secreto | Client Secret do app OAuth no RD Station |
RD_REFRESH_TOKEN |
Secreto | Refresh Token OAuth do RD Station |
Warning
Para editar secrets: Cloudflare → Workers → rd-leads → Configurações → Variáveis e segredos → clicar no ícone de lápis.
Rotas do Worker¶
Endereço base
https://api.grupoveggi.com.br
As rotas abaixo são o caminho depois desse endereço.
| Rota | Conversion Identifier | Formulário |
|---|---|---|
POST /convert/querorevender |
institucional_quero_revender |
Página "Quero Revender" |
POST /convert/ja-soucliente |
institucional_ja_sou_cliente |
Página "Já sou Cliente" |
Respostas de erro¶
| Status | Mensagem | Causa |
|---|---|---|
| 204 | (sem corpo) | Preflight CORS (OPTIONS) — comportamento normal |
| 400 | JSON inválido |
Body da requisição não é JSON válido |
| 400 | captcha obrigatório |
Campo turnstileToken ausente no body |
| 400 | email obrigatório |
Campo email ausente ou vazio |
| 403 | Forbidden origin |
Origin da requisição ≠ ALLOWED_ORIGIN |
| 403 | captcha inválido |
Token Turnstile rejeitado pela API da Cloudflare |
| 404 | Invalid route |
Rota não mapeada no Worker |
| 405 | Method Not Allowed |
Método diferente de POST ou OPTIONS |
| 502 | Erro no RD |
Falha na chamada à API do RD Station |
Fluxo de autenticação OAuth com RD Station¶
O Worker usa OAuth 2.0 com grant_type: refresh_token. O token de acesso é cacheado em memória com validade de ~1 hora (com margem de 60 segundos para renovação antecipada).
Warning
O cache é por instância do Worker. Em caso de reinicialização (cold start), um novo token é buscado automaticamente na primeira requisição.
Warning
Atenção ao trocar de domínio: se o site mudar de URL, o secret ALLOWED_ORIGIN precisa ser atualizado no Worker. Caso contrário, todos os formulários retornam 403 Forbidden origin.
Worker: veggi-validador-email¶
workers.dev: veggi-validador-email.veggiageral.workers.dev
Código: C:\Projetos\vtex-analise\validador\ · KV binding: CACHE
Valida o e-mail digitado no checkout da loja B2C useveggi.com.br. Bloqueia domínio descartável (card testing) e sugere correção quando o cliente erra o domínio (gmail.con -> gmail.com). É o componente ativo do Módulo 5 — Loja VTEX.
Rotas¶
Endereço base
https://veggi-validador-email.veggiageral.workers.dev
As rotas abaixo são o caminho depois desse endereço.
| Rota | Método | Função |
|---|---|---|
/validar |
POST | Recebe {"email":"..."} e retorna {permitir, status, risco, motivos[], sugestao, mensagem} |
/stats |
GET | Contadores do dia por status |
Variáveis¶
| Variável | Tipo | Valor / descrição |
|---|---|---|
ORIGENS_PERMITIDAS |
Texto | https://www.useveggi.com.br,https://useveggi.com.br,https://useveggi.myvtex.com — domínios autorizados a chamar o Worker pelo navegador (CORS) |
Warning
Se a loja mudar de domínio ou ganhar subdomínio novo, ORIGENS_PERMITIDAS precisa ser atualizado no wrangler.jsonc seguido de wrangler deploy — senão o checkout inteiro passa a falhar por CORS.
Info
observability está ligado no wrangler.jsonc. Sem isso não haveria como descobrir por que um cliente legítimo foi bloqueado.
O consumo é feito por um snippet colado no checkout6-custom.js da VTEX. Ver Validador de E-mail para deploy, blocklist e desligamento de emergência.
KV: CACHE¶
Namespace ID: 1a13f74b2f384fa484d2823e32ec9adc · Binding: CACHE
Guarda duas coisas:
| Chave | Conteúdo | TTL |
|---|---|---|
dom:{dominio} |
Resultado da consulta DNS/MX daquele domínio | 7 dias |
blocklist |
Array JSON de domínios bloqueados — atualizável sem deploy | permanente |
# bloquear (SUBSTITUI a lista inteira)
wrangler kv key put --binding=CACHE blocklist '["novo1.com","novo2.com"]' --remote
# limpar o cache de um domínio já consultado
wrangler kv key delete --binding=CACHE "dom:novo1.com" --remote
Danger
O put na chave blocklist sobrescreve a lista inteira. Sempre passe todos os domínios, não só o novo.
Pages: connect-hub¶
Repositório: veggidocs/seller-share-vault · Branch: main
Domínio pages.dev: connect-hub-dp6.pages.dev
Deploy: automático a cada push no main (~1 minuto)
Frontend React que serve como portal do cliente para visualização das fotos do pedido.
Para atualizar o frontend: fazer push no branch main do repositório veggidocs/seller-share-vault. O deploy ocorre automaticamente.
Pages: veggi-style-catalogue¶
Repositório: veggidocs/veggi-stylecatalogue · Branch: main
Deploy: automático a cada push no main
Frontend React do site institucional. Os formulários de captação enviam dados para o Worker rd-leads.
Variáveis de ambiente¶
| Variável | Tipo | Descrição |
|---|---|---|
VITE_TURNSTILE_SITE_KEY |
Texto | Chave pública do Turnstile (exibida no frontend) |
Warning
É necessário reimplantar após salvar variáveis de ambiente para que entrem em vigor.
KV: DOWNLOAD_STORAGE¶
Namespace ID: 837948d5f1f64663850322a0ef69b9e3
Account ID: 7519e532aced816970bc9f3fad392d3f
Cache de tokens de acesso. Armazena os dados de cada pedido indexados pelo token, com expiração automática. É a fonte de verdade para o Worker drive — sem o registro no KV, o link do cliente não funciona.
Estrutura de um registro no KV¶
Chave: {token} (string alfanumérica de 24 chars) · TTL: 45 dias (3.888.000 segundos)
{
"token": "abc123xyz...",
"numero_nf": "70570",
"arquivos": [
{
"nome": "foto1.jpg",
"path": "pasta/foto1.jpg",
"nome_produto": "Pijama Turma da Bia"
}
],
"expires_at": "2025-05-01T00:00:00.000Z",
"total_fotos": 5,
"cliente_nome": "Nome do Cliente",
"cliente_email": "cliente@email.com",
"view_url": "https://hub.grupoveggi.com.br/?token=abc123xyz...",
"zip_url": "https://drive.grupoveggi.com.br/abc123xyz..."
}
Tokens temporários de download¶
Quando o cliente seleciona arquivos no portal (POST /entrega), o Worker drive cria um segundo registro:
- Chave:
{token_original}-{uuid12chars}· TTL: 30 minutos - Conteúdo: somente os arquivos selecionados +
numero_nf+cliente_nome
Quem grava e quem lê o KV¶
| Operação | Quem faz | Quando |
|---|---|---|
| Gravar token principal (TTL 45 dias) | n8n — WF1 | Ao processar uma NF-e |
| Gravar token temporário (TTL 30 min) | Worker drive — rota /entrega |
Quando cliente seleciona arquivos |
| Ler token | Worker drive — rotas /api/token, /{token}, /{token}?download=1 |
Ao acessar o portal |
Workers de mídia paga (Módulos 6 a 8)¶
Quatro Workers que vivem no repositório veggi-ads-dashboard, cada um com deploy próprio. A documentação completa está nos módulos; aqui fica só o mapa de infraestrutura.
| Worker | KV | Crons | Secrets |
|---|---|---|---|
veggi-meta-updater |
VEGGI_DATA |
30 8 * * * e 0 9 * * * |
META_TOKEN, RD_CRM_TOKEN, UPDATE_KEY, VTEX |
veggi-mcp |
VEGGI_DATA |
— | MCP_TOKEN, META_TOKEN, RD_MKT_* |
veggi-enriquecedor |
ENRIQUECEDOR |
*/15 * * * * |
RD_CRM_TOKEN, CHAVE, SEGREDO_WEBHOOK |
veggi-atribuicao |
— | */30 * * * * |
RD_CRM_TOKEN, RD_MKT_CLIENT_ID, RD_MKT_CLIENT_SECRET, RD_MKT_REFRESH_TOKEN, CHAVE |
Limite de 5 crons na conta
O total é compartilhado entre todas as aplicações da conta. Hoje são 4 em uso (2 do updater, 1 do enriquecedor, 1 do atribuição). Só sobra 1. O enriquecedor faz duas tarefas diferentes dentro de um único cron justamente por causa desse teto — decide pelo horário.
Não junte os dois crons do updater
A Cloudflare limita chamadas externas por execução. Juntas, a leitura da VTEX e a da Meta estouravam o teto — a conta B2B chegou a sumir da dashboard por isso.
Pages veggi-meta — a porta dos fundos¶
functions/_middleware.js só deixa passar o host dashmeta.grupoveggi.com.br; veggi-meta.pages.dev e URLs de preview recebem 403. É o que impede os dados de vazarem pela URL pública do Pages, que não tem login. Nunca remover.
KV VEGGI_DATA¶
Namespace ID: 02f925d798f44173b1f0f2b9ea45c4db
| Chave | Quem grava | Conteúdo |
|---|---|---|
latest |
veggi-meta-updater |
Tudo que dashboard e consultor mostram |
crm_history |
veggi-meta-updater |
Histórico mensal do CRM |
consultor_memory |
veggi-mcp |
Memória do consultor |
Diagnóstico Cloudflare¶
Worker nfe¶
| Sintoma | Causa provável e ação |
|---|---|
| NF-e enviada por e-mail mas n8n não recebe nada | Verificar logs do Worker nfe em Cloudflare → Observability → Logs. Checar se o e-mail chegou em nfe@nfe.grupoveggi.com.br |
| Worker recebe mas webhook retorna erro | n8n pode estar fora do ar ou o webhook desativado. Verificar status do n8n e se o WF1 está ATIVO |
| XML não encontrado nos anexos | E-mail enviado sem anexo XML ou com content-type diferente. O Worker filtra por extensão .xml OU content-type contendo xml |
Worker drive¶
| Sintoma | Causa provável e ação |
|---|---|
| Link do cliente retorna 404 | Token não existe ou expirou no KV. Usar WF2 (Revalidação) para estender, ou reprocessar a NF-e |
| Portal carrega mas fotos não aparecem | Worker drive não consegue ler o KV ou retornou erro na rota /api/token. Verificar logs do Worker |
| Download ZIP falha ou fica travado | Arquivos não acessíveis via /file-proxy. Verificar se os paths no KV batem com a estrutura do bucket B2 |
| Fotos nos cards não carregam (erro 4xx) | buildImageUrl no frontend aponta para /file-proxy?path=. Verificar se o Worker drive está no ar e se o path do arquivo está correto |
Erro de autenticação no /entrega |
Header X-Drive-Secret errado ou ausente. O Pages deve enviar DRIVE_SECRET = veggi-drive-2026-xpto |
| Fotos aparecem mas algumas quebradas | Path do arquivo no Backblaze incorreto. Verificar campo path nos arquivos do registro KV |
Worker rd-leads¶
| Sintoma | Causa provável e ação |
|---|---|
| Formulário do site não envia | Abrir DevTools (F12) → Console → tentar enviar → verificar erros em vermelho |
Console mostra TurnstileError |
VITE_TURNSTILE_SITE_KEY não configurada no Pages ou valor incorreto. Verificar variável e reimplantar |
Worker retorna 403 Forbidden origin |
ALLOWED_ORIGIN desatualizado no Worker. Atualizar o secret → aguardar ~1 min |
Worker retorna 502 Erro no RD |
Token OAuth inválido ou Refresh Token expirado. Verificar secrets RD_CLIENT_ID, RD_CLIENT_SECRET, RD_REFRESH_TOKEN |
Worker veggi-validador-email¶
| Sintoma | Causa provável e ação |
|---|---|
| Cliente legítimo barrado no checkout | Ver logs em Workers → veggi-validador-email → Observability. Se urgente, remover o bloco do checkout6-custom.js na VTEX |
| Console do checkout mostra erro de CORS | Domínio da loja fora de ORIGENS_PERMITIDAS. Atualizar wrangler.jsonc → wrangler deploy |
| Domínio bloqueado continua passando | Resultado em cache por 7 dias. wrangler kv key delete --binding=CACHE "dom:<dominio>" --remote |
| Blocklist parou de barrar domínios antigos | Algum put sobrescreveu a lista. Regravar com a lista completa |
Workers de mídia paga¶
| Sintoma | Causa provável e ação |
|---|---|
| Dashboard de anúncios sem dados | O veggi-meta-updater não rodou. Disparar /update?key=... e ver Observability |
| Uma conta de anúncios sumiu da dashboard | Teto de sub-requisições estourado — conferir se os dois crons do updater continuam separados |
| Conector do Claude retorna 404 | MCP_TOKEN errado na URL do conector |
| Ferramenta nova do MCP não aparece | Conector não atualizado ou chat antigo. Atualizar e abrir chat novo |
| Enriquecedor calcula mas não grava | Trava de escrita desligada. Abrir / do Worker para ver o estado |
Pages connect-hub¶
| Sintoma | Causa provável e ação |
|---|---|
| Portal não abre / erro 500 | Verificar último deploy em Workers & Pages → connect-hub → Implantações. Se falhou, ver logs do build |
| Atualização do frontend não refletiu | Push no main não foi feito ou o deploy automático falhou. Verificar aba Implantações |
| CORS bloqueando requisições ao Worker | Worker drive retorna CORS dinâmico baseado no header Origin. Verificar se o domínio do Pages está na lista de origens permitidas |
Melhorias Sugeridas¶
Worker nfe¶
- Adicionar validação do remetente (domínio do ERP) para rejeitar e-mails de fontes não autorizadas
- Considerar retentativas (retry) no envio para o n8n — atualmente se o POST falhar, o XML é perdido
Worker drive¶
- Remover a variável
NFE_WEBHOOK_URLdo Workerdrive— ela está presente mas não é usada neste Worker - ~~Mover
DRIVE_SECRETde "Texto não criptografado" para "Secreto" no painel do Cloudflare~~ ✅ Concluído Abril/2026 - As imagens dos cards do portal agora passam pelo
/file-proxy(cache 1h). O Backblaze B2 não é mais acessado diretamente pelo frontend
Worker rd-leads¶
- Mover o
RD_REFRESH_TOKENpara rotação automática — tokens OAuth do RD Station podem expirar por inatividade prolongada - Considerar múltiplos valores em
ALLOWED_ORIGINpara suportar ambientes de homologação sem alterar produção
Pages¶
- Adicionar proteção de branch
mainno GitHub para evitar push direto sem revisão nos dois repositórios
Worker veggi-validador-email¶
- Chamar
/validartambém no lado server (API/webhook de captação de lead) — o snippet no navegador só segura o usuário honesto; bot não roda JavaScript - Automatizar a atualização da
blocklista partir da saída dovigia-dominios.py, hoje copiada e colada à mão
Workers de mídia paga¶
- Mover a chave do enriquecedor para fora do
MEUS-LINKS.txt— hoje ela fica em texto puro num arquivo local (está no.gitignore, mas é senha em arquivo) - Avaliar consolidar
latestem chaves menores: hoje é um JSON único que cresce a cada conta nova