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
KV Namespace DOWNLOAD_STORAGE Cache de tokens de acesso (TTL 45 dias)

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:

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

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.


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

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

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