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)¶
- 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:
| 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¶
| 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_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