{
  "name": "Webhook health check (unboxth.xyz)",
  "nodes": [
    {
      "id": "ebb3d4c3-d8cd-54ef-ba4a-a7a55f6121bc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -420
      ],
      "parameters": {
        "content": "## Webhook health check (unboxth.xyz)\n\n**1. Publish** this workflow. Production URLs only exist after you publish.\n\n**2. Call the production URL** (not /webhook-test/):\n`curl -i https://YOUR-N8N/webhook/unboxth-health-check`\n`curl -i -X POST https://YOUR-N8N/webhook/unboxth-health-check -H 'Content-Type: application/json' -d '{\"ping\":1}'`\n\n**3. Read the JSON reply:**\n- `n8nWebhookUrl` shows localhost or http? Set **N8N_WEBHOOK_URL** (older: WEBHOOK_URL) and restart.\n- `warnings` lists proxy/header problems.\n- No JSON at all? The request never reached this workflow: check DNS, proxy routing for /webhook/*, and queue-mode workers.\n\nAccepts GET and POST only. Path conflicts: change the path if another workflow uses it.\nChecked against docs.n8n.io on 2026-11-02. Unpublish when done.\n",
        "height": 380,
        "width": 640,
        "color": 5
      }
    },
    {
      "id": "9e488c18-9bd2-549e-9572-b824d5bd1f1c",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "b6481ecf-9414-5b5d-be0f-4c3beeb7d555",
      "parameters": {
        "multipleMethods": true,
        "httpMethod": [
          "GET",
          "POST"
        ],
        "path": "unboxth-health-check",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "ab17dbd6-5d63-5979-aee2-5a5c9413faf3",
      "name": "Build diagnostics",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// unboxth.xyz webhook health check. Echoes what n8n received and flags common\n// production-webhook problems. Safe to leave published; remove when done.\nconst req = $input.first().json;\nconst methods = ['GET', 'POST']; // must match the Webhook node's HTTP Methods order\nconst method = methods[$prevNode.outputIndex] || 'unknown';\nconst h = req.headers || {};\n\n// Never echo secrets back.\nconst redacted = {};\nfor (const [k, v] of Object.entries(h)) {\n  redacted[k] = ['authorization', 'cookie', 'x-api-key', 'proxy-authorization'].includes(k.toLowerCase())\n    ? '[redacted]' : v;\n}\n\nlet n8nUrl = null;\ntry { n8nUrl = new URL(req.webhookUrl); } catch (e) {}\n\nconst first = (v) => (v || '').toString().split(',')[0].trim();\nconst fwdProto = first(h['x-forwarded-proto']);\nconst fwdHost = first(h['x-forwarded-host']);\nconst publicHost = fwdHost || first(h['host']);\nconst warnings = [];\n\nif (n8nUrl && /^(localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0)$/.test(n8nUrl.hostname)) {\n  warnings.push('n8n builds its webhook URLs with ' + n8nUrl.host + '. Behind a reverse proxy, set N8N_WEBHOOK_URL (WEBHOOK_URL on older versions) to your public https URL and restart.');\n}\nif (n8nUrl && fwdProto && fwdProto + ':' !== n8nUrl.protocol) {\n  warnings.push('Proxy says the caller used ' + fwdProto + ' but n8n thinks its URL is ' + n8nUrl.protocol.replace(':', '') + '. Check N8N_WEBHOOK_URL.');\n}\nif (n8nUrl && publicHost && publicHost.split(':')[0] !== n8nUrl.hostname) {\n  warnings.push('Request arrived for host ' + publicHost + ' but n8n advertises ' + n8nUrl.hostname + '. Callers may be using a different URL than n8n shows.');\n}\nif (!h['x-forwarded-for'] && !h['x-forwarded-proto'] && !h['x-forwarded-host']) {\n  warnings.push('No X-Forwarded-* headers. Fine if nothing sits in front of n8n; if a proxy does, set X-Forwarded-For/Host/Proto there and N8N_PROXY_HOPS to the number of proxies.');\n}\nif (req.executionMode === 'test') {\n  warnings.push('This was a TEST call (/webhook-test/). Production callers must use /webhook/ and the workflow must be published.');\n}\n\nreturn [{\n  json: {\n    ok: true,\n    note: 'If you can read this, the request reached n8n and this workflow ran.',\n    receivedAt: new Date().toISOString(),\n    executionMode: req.executionMode,\n    method,\n    n8nWebhookUrl: req.webhookUrl,\n    publicHostSeen: publicHost || null,\n    forwarded: {\n      for: h['x-forwarded-for'] || null,\n      host: h['x-forwarded-host'] || null,\n      proto: h['x-forwarded-proto'] || null,\n    },\n    query: req.query || {},\n    params: req.params || {},\n    bodyKeys: req.body && typeof req.body === 'object' ? Object.keys(req.body) : [],\n    contentType: h['content-type'] || null,\n    headers: redacted,\n    warnings,\n  },\n}];\n"
      }
    },
    {
      "id": "8006b007-eb54-588d-9ead-e996c672a45c",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        520,
        0
      ],
      "parameters": {
        "respondWith": "firstIncomingItem",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Cache-Control",
                "value": "no-store"
              }
            ]
          }
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Build diagnostics",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build diagnostics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build diagnostics": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true
  }
}
