{
  "id": "jpSignalToSlack1",
  "name": "JobsPipe: Signal webhook to Slack",
  "nodes": [
    {
      "parameters": {
        "content": "## JobsPipe Signal to Slack\n\n1. Activate this workflow and copy the **Production URL** of the **JobsPipe Signal webhook** node.\n2. In the JobsPipe dashboard open **Workflows**, pick a template or describe the watch, choose **Webhook** as the destination and paste the URL.\n3. Copy the `whsec_` signing secret shown once after you create it, and paste it into the **Secret** field of **Compute HMAC**.\n4. Pick a Slack channel in **Post to Slack**.\n\nSignals cost no credits. Webhook destinations need a paid plan. Deliveries whose signature or timestamp do not check out are dropped.",
        "height": 380,
        "width": 600
      },
      "id": "919ac13e-bbf7-5a44-8886-0d3da88de87a",
      "name": "About this template",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -260,
        -440
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "jobspipe-signal",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "id": "bc3de7f2-0fa9-54a6-9fac-b95e7f545cc1",
      "name": "JobsPipe Signal webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "ce66c481-de32-5bd3-a048-d01396cbe1ac"
    },
    {
      "parameters": {
        "jsCode": "const out = [];\nfor (let i = 0; i < $input.all().length; i++) {\n  const item = $input.all()[i];\n  const headers = item.json.headers || {};\n  const timestamp = String(headers['x-jobspipe-timestamp'] || '');\n  const signature = String(headers['x-jobspipe-signature'] || '');\n  const raw = (await this.helpers.getBinaryDataBuffer(i, 'data')).toString('utf8');\n  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));\n  out.push({\n    json: {\n      signed_payload: `${timestamp}.${raw}`,\n      signature,\n      fresh: timestamp !== '' && ageSeconds <= 300,\n      payload: item.json.body,\n    },\n  });\n}\nreturn out;"
      },
      "id": "1ea9febf-0ecf-522e-a164-4fe0da1ae8e8",
      "name": "Read signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "action": "hmac",
        "type": "SHA256",
        "value": "={{ $json.signed_payload }}",
        "dataPropertyName": "expected_signature",
        "secret": "whsec_REPLACE_WITH_YOUR_SIGNING_SECRET",
        "encoding": "hex"
      },
      "id": "532777ca-7bad-5237-99da-98482a534afe",
      "name": "Compute HMAC",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "4f1389de-40e7-5d19-bfff-ff16d5c711f3",
              "leftValue": "={{ $json.expected_signature }}",
              "rightValue": "={{ $json.signature }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "f4431389-a545-5be1-8399-9d8e5411caf4",
              "leftValue": "={{ $json.fresh }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "b398a275-e8ca-585c-88d6-891bc379b070",
              "leftValue": "={{ $json.payload.event }}",
              "rightValue": "alert.matched",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8bff62fe-9e9f-5229-a088-588596e0ebe8",
      "name": "Signature valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "payload.data",
        "options": {}
      },
      "id": "55858dc4-2b63-5e8a-9e7a-37fbb32eb356",
      "name": "One item per match",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        880,
        -100
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "",
          "mode": "list"
        },
        "text": "=*{{ $json.job_title }}* at *{{ $json.company }}*{{ $json.location ? ' - ' + $json.location : '' }}\n{{ $json.url }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false
        }
      },
      "id": "8a4d49f9-c2c2-5875-bd3a-a7ca69c868ec",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1100,
        -100
      ],
      "credentials": {
        "slackApi": {
          "name": "Slack account"
        }
      }
    },
    {
      "parameters": {},
      "id": "deeaec94-931d-5eee-ad85-3a7f77393d89",
      "name": "Ignore delivery",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        880,
        120
      ]
    }
  ],
  "connections": {
    "JobsPipe Signal webhook": {
      "main": [
        [
          {
            "node": "Read signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read signature": {
      "main": [
        [
          {
            "node": "Compute HMAC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute HMAC": {
      "main": [
        [
          {
            "node": "Signature valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "One item per match": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signature valid?": {
      "main": [
        [
          {
            "node": "One item per match",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignore delivery",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
