{
  "name": "Nodbot - Webhook HMAC signature validation",
  "nodes": [
    {
      "parameters": {
        "path": "webhook-signature-validation",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "id": "e61347d0-820c-4843-8ca4-859e086ef5c3",
      "name": "Webhook input",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const crypto = require('crypto');\nconst body = $json.rawBody ?? JSON.stringify($json.body ?? $json);\nconst headers = $json.headers ?? {};\nconst timestamp = Number(headers['x-webhook-timestamp'] ?? headers['X-Webhook-Timestamp']);\nconst signature = String(headers['x-webhook-signature'] ?? headers['X-Webhook-Signature'] ?? '');\nconst secret = $env.WEBHOOK_SIGNING_SECRET;\n\nif (!secret) throw new Error('WEBHOOK_SIGNING_SECRET is not configured');\nif (!timestamp || Math.abs(Date.now() - timestamp * 1000) > 5 * 60 * 1000) {\n  throw new Error('Webhook timestamp is outside 5 minute tolerance');\n}\n\nconst expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');\nconst ok = signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));\n\nif (!ok) throw new Error('Invalid webhook signature');\n\nconst parsed = typeof $json.body === 'object' ? $json.body : JSON.parse(body);\nreturn [{ json: { verified: true, replay_key: `webhook:${parsed.event_id}`, event: parsed.event, body: parsed } }];"
      },
      "id": "bb7ed022-5914-44b0-a212-3a85e68e6955",
      "name": "Verify HMAC signature Code",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {},
      "id": "86e37476-0609-4b44-b660-d9299410bf85",
      "name": "Check replay key Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {},
      "id": "48eda39a-37f6-44cf-a2bd-9bff53bdc078",
      "name": "Business logic HTTP",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {},
      "id": "b7c3d87d-d856-428d-9581-2f00aa186a46",
      "name": "Respond safe status",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    }
  ],
  "connections": {
    "Webhook input": {
      "main": [
        [
          {
            "node": "Verify HMAC signature Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HMAC signature Code": {
      "main": [
        [
          {
            "node": "Check replay key Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check replay key Postgres": {
      "main": [
        [
          {
            "node": "Business logic HTTP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Business logic HTTP": {
      "main": [
        [
          {
            "node": "Respond safe status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "production",
    "template"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "b1752272-2a34-4441-bc93-2fd8b653c5f6",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}