Ir para o conteúdo

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)

  1. Recebe o e-mail bruto (RFC822) via handler email()
  2. Parseia o MIME completo — separa headers, corpo e partes
  3. Filtra apenas anexos com content-type XML ou extensão .xml
  4. Decodifica o corpo do anexo (suporta base64, quoted-printable, 7bit/8bit)
  5. Monta um payload JSON no formato compatível com o n8n
  6. Faz POST para o webhook do n8n com o payload
  7. 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

  1. Valida o header X-Drive-Secret contra a variável DRIVE_SECRET
  2. Lê o token principal do KV
  3. Gera um token temporário de download: {token}-{uuid12chars}
  4. Salva no KV com TTL de 30 minutos apenas os arquivos selecionados
  5. Retorna a download_url com 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):

  1. Busca a lista de arquivos do token no KV
  2. Baixa cada arquivo via /file-proxy com retry automático (3 tentativas)
  3. Usa a biblioteca client-zip (ESM via esm.sh) para montar o ZIP no navegador
  4. Oferece o download automático do arquivo Pedido_{numeroNF}.zip
  5. 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_URL do Worker drive — ela está presente mas não é usada neste Worker
  • ~~Mover DRIVE_SECRET de "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_TOKEN para rotação automática — tokens OAuth do RD Station podem expirar por inatividade prolongada
  • Considerar múltiplos valores em ALLOWED_ORIGIN para suportar ambientes de homologação sem alterar produção

Pages

  • Adicionar proteção de branch main no GitHub para evitar push direto sem revisão nos dois repositórios

Worker veggi-validador-email

  • Chamar /validar també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 blocklist a partir da saída do vigia-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 latest em chaves menores: hoje é um JSON único que cresce a cada conta nova