{
  "name": "Nodbot - GitHub webhook router with idempotency",
  "nodes": [
    {
      "parameters": {
        "path": "integration-github-n8n-issue-release-automation",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "id": "916baf93-3836-414f-bd6f-e7d529d398b3",
      "name": "GitHub Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst headers = $json.headers ?? src.headers ?? {};\nconst event = String(headers['x-github-event'] ?? headers['X-GitHub-Event'] ?? src.event ?? '').trim();\nconst delivery = String(headers['x-github-delivery'] ?? headers['X-GitHub-Delivery'] ?? src.delivery_id ?? '').trim();\nconst repo = String(src.repository?.full_name ?? '').trim().toLowerCase();\nconst allowedRepos = ['acme/payment-api', 'acme/web'];\nif (!allowedRepos.includes(repo)) throw new Error(`Repository is not allowed: ${repo}`);\nif (!delivery) throw new Error('No GitHub delivery id');\nconst action = String(src.action ?? '').trim();\nconst allowed = event === 'issues' && ['opened', 'labeled', 'reopened'].includes(action);\nif (!allowed) return [{ json: { action: 'ignore', reason: 'event_not_routed', event, github_action: action, repo } }];\nconst issue = src.issue ?? {};\nconst idempotencyKey = `github:${repo}:${event}:${delivery}`;\nreturn [{ json: {\n  action: 'route_issue',\n  idempotency_key: idempotencyKey,\n  repo,\n  event,\n  github_action: action,\n  issue_number: issue.number,\n  issue_url: issue.html_url,\n  title: String(issue.title ?? '').slice(0, 180),\n  labels: (issue.labels ?? []).map(l => l.name).filter(Boolean),\n  audit_comment: `n8n processed delivery ${delivery}`\n}}];"
      },
      "id": "d6d3604a-dce2-44e3-bfd9-77f9d8cc2eee",
      "name": "Validate GitHub event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {},
      "id": "76d2650d-1caf-4a0a-ba5e-3e6f11d27c8a",
      "name": "Check idempotency",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst headers = $json.headers ?? src.headers ?? {};\nconst event = String(headers['x-github-event'] ?? headers['X-GitHub-Event'] ?? src.event ?? '').trim();\nconst delivery = String(headers['x-github-delivery'] ?? headers['X-GitHub-Delivery'] ?? src.delivery_id ?? '').trim();\nconst repo = String(src.repository?.full_name ?? '').trim().toLowerCase();\nconst allowedRepos = ['acme/payment-api', 'acme/web'];\nif (!allowedRepos.includes(repo)) throw new Error(`Repository is not allowed: ${repo}`);\nif (!delivery) throw new Error('No GitHub delivery id');\nconst action = String(src.action ?? '').trim();\nconst allowed = event === 'issues' && ['opened', 'labeled', 'reopened'].includes(action);\nif (!allowed) return [{ json: { action: 'ignore', reason: 'event_not_routed', event, github_action: action, repo } }];\nconst issue = src.issue ?? {};\nconst idempotencyKey = `github:${repo}:${event}:${delivery}`;\nreturn [{ json: {\n  action: 'route_issue',\n  idempotency_key: idempotencyKey,\n  repo,\n  event,\n  github_action: action,\n  issue_number: issue.number,\n  issue_url: issue.html_url,\n  title: String(issue.title ?? '').slice(0, 180),\n  labels: (issue.labels ?? []).map(l => l.name).filter(Boolean),\n  audit_comment: `n8n processed delivery ${delivery}`\n}}];"
      },
      "id": "26b7bb15-bcbb-40da-9230-70c3a49ca933",
      "name": "Route GitHub action",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {},
      "id": "2e5062b1-7a74-47bf-a7b6-f05a74552414",
      "name": "Call GitHub API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {},
      "id": "7a6f4336-2e9b-4722-9ec4-24850a8edcd2",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1400,
        260
      ]
    }
  ],
  "connections": {
    "GitHub Webhook": {
      "main": [
        [
          {
            "node": "Validate GitHub event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate GitHub event": {
      "main": [
        [
          {
            "node": "Check idempotency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check idempotency": {
      "main": [
        [
          {
            "node": "Route GitHub action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route GitHub action": {
      "main": [
        [
          {
            "node": "Call GitHub API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call GitHub API": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "integration",
    "production"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "c65b925b-c1a3-4282-957d-d622fd6841f3",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}