{
  "name": "Rumo CRM: contato novo vira negócio",
  "nodes": [
    {
      "parameters": {
        "content": "## Rumo CRM: contato novo vira negócio\n\nRecebe o webhook `contact.created` do Rumo CRM, confere a assinatura `Rumo-Signature` e cria um negócio para o contato pela API v1.\n\n**Variáveis de ambiente do n8n**\n- `RUMO_WEBHOOK_SECRET`: a chave do webhook (Configurações → Integrações → Técnico → Webhooks)\n- `RUMO_TOKEN`: token pessoal com o escopo `crm:write` (Configurações → API e integrações)\n- `RUMO_PIPELINE_ID`: id do funil onde o negócio nasce (GET /pipelines)\n- Self-hosted: `NODE_FUNCTION_ALLOW_BUILTIN=crypto` e `N8N_BLOCK_ENV_ACCESS_IN_NODE=false`\n\n**No Rumo CRM**, cadastre a URL de produção deste Webhook com o evento `contact.created`.\n\nAssinatura inválida responde 401. Outros eventos respondem 200 e são ignorados. Se a criação falhar, o n8n responde erro e o Rumo CRM reenvia; a `Idempotency-Key` fixa por evento impede negócio duplicado.",
        "height": 420,
        "width": 520
      },
      "name": "Instruções",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        -320
      ],
      "id": "5f0a1c3e-0001-4a51-9b6e-000000000001"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "rumo-crm",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "name": "Webhook do Rumo CRM",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        200
      ],
      "id": "5f0a1c3e-0002-4a51-9b6e-000000000002",
      "webhookId": "5f0a1c3e-00a0-4a51-9b6e-0000000000a0"
    },
    {
      "parameters": {
        "jsCode": "// Confere a Rumo-Signature: t=<unix>,v1=<HMAC-SHA256 hex de \"<t>.<corpo cru>\">.\n// n8n self-hosted: NODE_FUNCTION_ALLOW_BUILTIN=crypto e N8N_BLOCK_ENV_ACCESS_IN_NODE=false.\nconst crypto = require('crypto');\nconst SEGREDO = $env.RUMO_WEBHOOK_SECRET; // a chave de 64 caracteres mostrada ao criar o webhook\nconst TOLERANCIA_SEGUNDOS = 300;\n\nconst item = $input.first();\n// Corpo cru (opção Raw Body do Webhook): qualquer byte diferente muda a assinatura.\nconst corpo = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst cabecalho = String(item.json.headers['rumo-signature'] || '');\n\nlet timestamp = NaN;\nconst assinaturas = [];\nfor (const parte of cabecalho.split(',')) {\n  const separador = parte.indexOf('=');\n  if (separador < 0) continue;\n  const chave = parte.slice(0, separador).trim();\n  const valor = parte.slice(separador + 1).trim();\n  if (chave === 't' && /^\\d+$/.test(valor)) timestamp = Number(valor);\n  if (chave === 'v1' && valor) assinaturas.push(valor);\n}\n\nconst agora = Math.floor(Date.now() / 1000);\nlet valida = false;\nif (SEGREDO && Number.isInteger(timestamp) && Math.abs(agora - timestamp) <= TOLERANCIA_SEGUNDOS) {\n  const esperada = crypto.createHmac('sha256', SEGREDO).update(`${timestamp}.`).update(corpo).digest();\n  valida = assinaturas.some((hex) => {\n    const recebida = Buffer.from(hex, 'hex');\n    return recebida.length === esperada.length && crypto.timingSafeEqual(recebida, esperada);\n  });\n}\n\nreturn [{ json: { valida, evento: valida ? JSON.parse(corpo.toString('utf8')) : null } }];"
      },
      "name": "Verificar assinatura",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        200
      ],
      "id": "5f0a1c3e-0003-4a51-9b6e-000000000003"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "5f0a1c3e-00c1-4a51-9b6e-0000000000c1",
              "leftValue": "={{ $json.valida }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "Assinatura válida?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        440,
        200
      ],
      "id": "5f0a1c3e-0004-4a51-9b6e-000000000004"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "5f0a1c3e-00c2-4a51-9b6e-0000000000c2",
              "leftValue": "={{ $json.evento.event === 'contact.created' }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "É contact.created?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        660,
        100
      ],
      "id": "5f0a1c3e-0005-4a51-9b6e-000000000005"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://rumocrm.com/api/v1/deals",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.RUMO_TOKEN }}"
            },
            {
              "name": "Idempotency-Key",
              "value": "=n8n-{{ $json.evento.id }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ title: 'Lead: ' + ($json.evento.data.name || $json.evento.data.phoneNumber || $json.evento.data.email || 'sem nome'), pipelineId: $env.RUMO_PIPELINE_ID, contactId: $json.evento.data.contactId }) }}",
        "options": {}
      },
      "name": "Criar negócio",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ],
      "id": "5f0a1c3e-0006-4a51-9b6e-000000000006",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "respondWith": "noData",
        "options": {}
      },
      "name": "Responder 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1100,
        100
      ],
      "id": "5f0a1c3e-0007-4a51-9b6e-000000000007"
    },
    {
      "parameters": {
        "respondWith": "noData",
        "options": {
          "responseCode": 401
        }
      },
      "name": "Responder 401",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        660,
        320
      ],
      "id": "5f0a1c3e-0008-4a51-9b6e-000000000008"
    }
  ],
  "connections": {
    "Webhook do Rumo CRM": {
      "main": [
        [
          {
            "node": "Verificar assinatura",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verificar assinatura": {
      "main": [
        [
          {
            "node": "Assinatura válida?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assinatura válida?": {
      "main": [
        [
          {
            "node": "É contact.created?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responder 401",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "É contact.created?": {
      "main": [
        [
          {
            "node": "Criar negócio",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responder 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Criar negócio": {
      "main": [
        [
          {
            "node": "Responder 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}
