{
  "name": "Lanterna: receber webhook e validar a assinatura",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lanterna",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "b2c7f1b3-0000-4000-8000-000000000001",
      "name": "Webhook da Lanterna",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "webhookId": "lanterna"
    },
    {
      "parameters": {
        "jsCode": "// Valida a assinatura do webhook da Lanterna.\n// Requer NODE_FUNCTION_ALLOW_BUILTIN=crypto no n8n (módulo crypto liberado no Code node).\nconst crypto = require('crypto');\nconst SEGREDO = 'whsec_lan_COLE_O_SEGREDO_DE_CONTA_API'; // Conta > API > Segredo de assinatura (prefira guardar numa credencial)\n\nconst item = $input.first().json;\nconst header = item.headers['lanterna-signature'] || '';\nconst t = (/(?:^|,)t=(\\d+)/.exec(header) || [])[1];\nconst v1 = (/(?:^|,)v1=([0-9a-f]+)/.exec(header) || [])[1];\nif (!t || !v1) throw new Error('Assinatura ausente ou malformada');\nif (Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error('Assinatura antiga demais (mais de 5 minutos)');\n\n// A assinatura cobre o corpo exato enviado. JSON.stringify(corpo) reproduz o mesmo texto compacto na imensa maioria dos casos;\n// para ter certeza absoluta, ative 'Raw Body' no nó Webhook e use o corpo bruto.\nconst corpo = JSON.stringify(item.body);\nconst esperado = crypto.createHmac('sha256', SEGREDO).update(t + '.' + corpo).digest('hex');\nconst ok = esperado.length === v1.length && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));\nif (!ok) throw new Error('Assinatura inválida: webhook recusado');\n\nconst evento = item.body;\nreturn [{ json: { event: evento.event, event_id: evento.id, video: evento.data, mp4_url: evento.data && evento.data.result ? evento.data.result.mp4_url : null } }];"
      },
      "id": "b2c7f1b3-0000-4000-8000-000000000002",
      "name": "Validar assinatura",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [240, 0]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
                "conditions": [{ "id": "r1", "leftValue": "={{ $json.event }}", "rightValue": "video.completed", "operator": { "type": "string", "operation": "equals" } }],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Pronto"
            },
            {
              "conditions": {
                "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
                "conditions": [{ "id": "r2", "leftValue": "={{ $json.event }}", "rightValue": "video.failed", "operator": { "type": "string", "operation": "equals" } }],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Falhou"
            }
          ]
        },
        "options": { "fallbackOutput": "extra" }
      },
      "id": "b2c7f1b3-0000-4000-8000-000000000003",
      "name": "Qual evento?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [480, 0]
    },
    {
      "parameters": {},
      "id": "b2c7f1b3-0000-4000-8000-000000000004",
      "name": "Usar o MP4 (aqui)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [720, -120],
      "notes": "Use {{ $json.mp4_url }} (link assinado, 24 h) e {{ $json.video.result.publication_text }} nos próximos nós."
    },
    {
      "parameters": {},
      "id": "b2c7f1b3-0000-4000-8000-000000000005",
      "name": "Tratar falha (aqui)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [720, 0]
    },
    {
      "parameters": {},
      "id": "b2c7f1b3-0000-4000-8000-000000000006",
      "name": "Outros eventos",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [720, 120]
    }
  ],
  "connections": {
    "Webhook da Lanterna": { "main": [[{ "node": "Validar assinatura", "type": "main", "index": 0 }]] },
    "Validar assinatura": { "main": [[{ "node": "Qual evento?", "type": "main", "index": 0 }]] },
    "Qual evento?": {
      "main": [
        [{ "node": "Usar o MP4 (aqui)", "type": "main", "index": 0 }],
        [{ "node": "Tratar falha (aqui)", "type": "main", "index": 0 }],
        [{ "node": "Outros eventos", "type": "main", "index": 0 }]
      ]
    }
  },
  "pinData": {},
  "settings": { "executionOrder": "v1" },
  "meta": { "templateCredsSetupCompleted": false }
}
