revflow

Node community do n8n para o RevFlow, CRM multi-tenant brasileiro (Company, Contact, Deal, Lead, Ticket, Task via /api/v1)

Documentation

n8n-nodes-revflow

Node community do n8n para o RevFlow, CRM multi-tenant
brasileiro. Consome a API pública /api/v1/* do RevFlow (contrato completo em
../API_CONTRACT.md, na raiz de integrations/).

Status: publicado — n8n-nodes-revflow@0.2.0
está no registry do npm (publicado 2026-08-30, token de automação
fornecido pelo dono do RevFlow naquela sessão — não persiste entre
sessões, ver seção "Publicando de verdade" pra publicar uma versão nova
depois de mudar o código de novo). Instalável via
npm install n8n-nodes-revflow ou pela aba Settings → Community Nodes
do próprio n8n.

Changelog

  • 0.2.0 (2026-08-30, publicada) — RevFlow Trigger migrado de polling
    (poll()) pra webhook em tempo real (webhookMethods + webhook()),
    consumindo a API de webhooks nova (POST /api/v1/webhooks). Ver seção
    "RevFlow Trigger precisa de URL pública" abaixo antes de testar
    localmente.
  • 0.1.0 (2026-08-30, publicada) — primeira versão. RevFlow (ação) +
    RevFlow Trigger (polling).

O que este pacote faz

  • Credencial RevFlow API (credentials/RevFlowApi.credentials.ts): guarda
    a chave de API (rfk_...) e a Base URL (padrão
    https://crmrevflow.vercel.app/api/v1, editável). Injeta o header
    Authorization: Bearer <chave> em toda chamada e testa a credencial contra
    GET /companies?limit=1.
  • Node RevFlow (nodes/RevFlow/RevFlow.node.ts): node de ação
    programático (não declarative-only) com os 6 recursos do contrato —
    Company, Contact, Deal, Lead, Ticket, Task — e as operações que a API
    realmente oferece por objeto:
    • Create (POST), Update (PATCH /{id}), Get (GET /{id}),
      Get Many (GET lista, com Since/Limit/Return All);
    • Find By Email (GET ?email=&limit=1) só em Company, Contact e Lead —
      os 3 únicos objetos com coluna email.
    • Sem operação de exclusão em nenhum recurso (a API pública não tem
      DELETE) e sem campo para trocar a Company associada num Update de
      Contact/Deal/Ticket (a API só aceita isso na criação).
  • Node RevFlow Trigger (nodes/RevFlowTrigger/RevFlowTrigger.node.ts):
    trigger de webhook em tempo real desde a v0.2.0 (2026-08-30) — a API
    pública ganhou webhooks de saída (POST /api/v1/webhooks, ver
    API_CONTRACT.md). Ao ativar o workflow, o node se inscreve
    (webhookMethods.create, chamado com a própria URL de webhook do n8n) e
    passa a receber um POST assinado (HMAC-SHA256, header
    X-RevFlow-Signature: sha256=<hex>) a cada registro novo do objeto
    escolhido — sem cursor since, sem intervalo de checagem. webhook()
    valida a assinatura contra os bytes crus da requisição
    (req.rawBody, preenchido pelo próprio middleware do n8n antes do parse
    — nunca reserializa o JSON pra comparar, o que arriscaria produzir um
    texto diferente do que foi assinado) antes de emitir o item; assinatura
    inválida ou ausente → 401, sem disparar o workflow.
    webhookMethods.delete cancela a inscrição (DELETE /api/v1/webhooks/{id})
    quando o workflow é desativado ou o node é removido; checkExists
    confirma que a inscrição salva ainda existe do lado da RevFlow antes de
    decidir se recria (ex.: alguém revogou manualmente em /chaves-de-api).
    Versão anterior (v0.1.0, version: 1) fazia polling (poll(),
    cursor since em getWorkflowStaticData) — descontinuada sem
    IVersionedNodeType (sem base real de workflow em produção usando a
    v1 no momento da mudança, ver comentário no topo do arquivo do node).

Nenhum campo ou endpoint aqui foi inventado — tudo foi implementado lendo
API_CONTRACT.md inteiro antes de escrever código.

Rodando localmente

cd integrations/n8n
npm install
npm run build   # tsc -> dist/
npm run lint    # eslint-plugin-n8n-nodes-base (preset oficial de community node)
npm test        # jest, com mocks do contexto do n8n (sem subir n8n de verdade)

Os três passam limpos neste ambiente (Node 22, TypeScript 5.9, n8n-workflow
2.16 — a versão retornada por npm view n8n-workflow version no momento em
que este pacote foi criado).

Sobre o lint

O preset eslint-plugin-n8n-nodes-base é o mesmo conjunto de regras usado
pela verificação automática do n8n quando o pacote é instalado ou submetido
para a listagem de community nodes — por isso ele está configurado aqui (não
foi preciso pular por falta de rede; o pacote e o @typescript-eslint/parser
que ele precisa foram instalados normalmente do registry).

Uma única regra do preset foi desligada deliberadamente, com o motivo
documentado em .eslintrc.js:
n8n-nodes-base/cred-class-field-documentation-url-miscased. A própria
descrição da regra diz "Only applicable to nodes in the main repository" — ela
assume que documentationUrl é um slug interno do site de docs do n8n e tenta
"corrigir" qualquer outra coisa convertendo para camelCase, o que destrói uma
URL real de community node (ela transformava
https://crmrevflow.vercel.app/chaves-de-api em
httpsCrmrevflowVercelAppChavesDeApi). Todas as outras regras do preset
(nodes, credentials e package.json) passam sem exceção.

Testando dentro de uma instância n8n local

Depois de npm run build, existem duas formas de expor o pacote pra uma
instância n8n rodando na sua máquina:

Opção 1 — npm link

cd integrations/n8n
npm link

# numa instância n8n local (self-hosted, via npm/pnpm, não Docker Cloud):
cd ~/.n8n           # ou onde estiver instalado o n8n
npm link n8n-nodes-revflow

Depois, suba o n8n com a variável de ambiente apontando pra onde os
community nodes ficam:

export N8N_CUSTOM_EXTENSIONS="$(npm root -g)/n8n-nodes-revflow"
n8n start

(Ajuste o caminho se o seu npm link resolver para outro diretório — rode
npm ls -g n8n-nodes-revflow pra confirmar.)

Opção 2 — copiar pra ~/.n8n/custom

mkdir -p ~/.n8n/custom
cp -r integrations/n8n ~/.n8n/custom/n8n-nodes-revflow
cd ~/.n8n/custom/n8n-nodes-revflow
npm install --omit=dev
npm run build
n8n start

Em qualquer uma das duas opções, os nodes RevFlow e RevFlow Trigger
devem aparecer na busca de nodes do editor do n8n após reiniciar.

RevFlow Trigger precisa de URL pública (desde a v0.2.0)

Diferente da v0.1.0 (polling — o n8n é quem chamava a RevFlow, de dentro
pra fora), o RevFlow Trigger agora é um webhook de verdade: ao ativar
o workflow, a RevFlow passa a chamar de fora pra dentro da sua
instância n8n a cada registro novo. Uma instância local
(http://localhost:5678) não é alcançável pela internet — pra testar de
ponta a ponta (não só os testes automatizados com mock), exponha-a com um
túnel (n8n start --tunnel, ou ngrok http 5678 + configurar
WEBHOOK_URL com a URL pública) antes de ativar o workflow. Sem isso, a
inscrição em POST /api/v1/webhooks é criada normalmente, mas a entrega
nunca chega — o destino (target_url) simplesmente não responde.

Testando com uma chave de API de verdade

Pra testar de ponta a ponta (não só os testes automatizados com mock), você
precisa de uma chave de API rfk_... gerada numa organização RevFlow real,
em RevFlow → Chaves de API (/chaves-de-api, como administrador da
organização). Sem isso:

  • Get Many/Get/Find By Email em Company, Contact, Task funcionam com
    qualquer chave válida (objetos Core, sem módulo pago).
  • Deal, Lead, Ticket exigem, respectivamente, os módulos pagos sales,
    marketing e services ativos pra organização da chave — sem isso a API
    responde 403 (o node propaga essa mensagem, não a esconde).

Nunca commite uma chave de API real em nenhum lugar deste repositório —
nem em código, nem em fixtures de teste, nem em exemplos de README. Use apenas
via credencial configurada na UI do n8n (que fica fora do controle de versão).

Publicando de verdade (o que só você pode fazer)

Eu não tenho conta npm nem acesso à verificação de community node do n8n —
os passos abaixo dependem de uma pessoa:

  1. Criar/logar numa conta npm própria: npm login (o pacote precisa ser
    publicado sob um nome ou escopo que pertença a você — hoje ele se chama
    n8n-nodes-revflow; se esse nome já estiver em uso por outra pessoa no
    registry público, será preciso escopar, ex.: @sua-org/n8n-nodes-revflow,
    e então também ajustar package.json → n8n → os caminhos continuam os
    mesmos, só o name muda). Ajuste também repository.url e author em
    package.json — os valores atuais (github.com/revflow/n8n-nodes-revflow,
    autor "RevFlow") são placeholders escritos durante o desenvolvimento, não
    uma conta ou repositório real.
  2. Publicar: depois de npm run build (o script prepublishOnly já roda
    build + test automaticamente antes do publish, mas rode manualmente antes
    pra revisar o npm pack --dry-run primeiro):
    npm run build
    npm test
    npm publish
    
  3. Submeter pra listagem oficial de community nodes do n8n: hoje isso
    acontece automaticamente quando alguém instala o pacote (o n8n roda uma
    verificação own no momento da instalação) — pra aparecer na lista
    pesquisável
    de community nodes dentro do editor do n8n, existe também um
    processo de submissão / pedido de inclusão na lista "verified", que exige
    aprovação da equipe do n8n. Isso está fora do meu controle e do desta
    automação — acompanhe a documentação oficial do n8n sobre "community
    nodes" no momento da publicação, pois o processo evolui com o tempo.
  4. Versionamento: depois do primeiro publish, qualquer mudança exige subir
    version em package.json (npm version patch|minor|major) antes de
    publicar de novo — o npm registry rejeita republicar a mesma versão.

Estrutura do pacote

integrations/n8n/
├── credentials/
│   ├── RevFlowApi.credentials.ts
│   └── RevFlowApi.credentials.test.ts
├── nodes/
│   ├── RevFlow/
│   │   ├── RevFlow.node.ts
│   │   ├── RevFlow.node.test.ts
│   │   ├── GenericFunctions.ts       # requisição HTTP + tratamento de erro compartilhados
│   │   └── revflow.svg               # ícone (não é a marca real — "RF" genérico)
│   └── RevFlowTrigger/
│       ├── RevFlowTrigger.node.ts
│       ├── RevFlowTrigger.node.test.ts
│       └── revflow.svg
├── test/
│   └── mockContext.ts                # fábrica de contexto falso p/ testar nodes sem subir n8n
├── package.json
├── tsconfig.json
├── jest.config.js
└── .eslintrc.js

Decisões de design não 100% explícitas no contrato

  • Return All não é um export completo da base: a API não pagina por
    cursor (API_CONTRACT.md, "Padrão de gatilho"). O node segue o padrão
    idiomático Return All/Limit do n8n, mas com Return All marcado ele
    busca exatamente o teto de 200 registros mais recentes — nunca mais que
    isso. Isso está documentado no campo "Since" e na descrição da operação
    "Get Many", já que a descrição do próprio campo Limit/Return All segue
    o texto padrão exigido pelo preset de lint do n8n (não deu pra colocar a
    ressalva ali sem violar a regra node-param-description-wrong-for-limit).
  • Validação client-side de pipeline_id/stage_id: a API já rejeita com
    400 se só um dos dois for informado (Deal/Ticket), mas o node valida isso
    antes de chamar a API, poupando uma chamada e dando um erro mais claro.
  • Validação client-side de "Update Fields" vazio: mesma lógica — a API já
    rejeita PATCH sem nenhum campo com 400, o node antecipa isso.
  • Verificação de assinatura do RevFlow Trigger usa req.rawBody, nunca
    getBodyData() reserializado
    : comparar HMAC contra
    JSON.stringify(getBodyData()) arriscaria produzir um texto diferente do
    que a RevFlow assinou de fato (ordem de chave, formatação de número) —
    invalidaria a verificação por um falso negativo. req.rawBody é
    preenchido pelo próprio middleware de body-parsing do n8n
    (rawBodyReader, packages/cli/src/middlewares/body-parser.ts) antes do
    JSON ser parseado, mas não está no tipo público express.Request de
    IWebhookFunctions.getRequestObject() — daqui o cast explícito no node.
  • Sem IVersionedNodeType na migração de polling pra webhook: a v0.1.0
    (só poll(), publicada 1 dia antes) não tinha base real de workflow em
    produção — manter as duas versões vivas lado a lado (nodeVersions)
    adicionaria complexidade sem ninguém pra proteger. RevFlowTrigger foi
    direto pra version: 2 como node único, comportamento novo pra quem
    reativar o workflow depois do upgrade.
  • Erros de API: GenericFunctions.ts extrai a mensagem { "error": "..." }
    da API e monta um NodeApiError com essa mensagem como texto principal
    (nunca escondida atrás de "Request failed"), com um destaque extra pro
    header Retry-After em erros 429. continueOnFail funciona normalmente
    porque o erro só é capturado no loop principal do execute(), nunca dentro
    de GenericFunctions.ts.

Discussion