{
  "name": "Coolie: lead replied → Slack",
  "nodes": [
    {
      "parameters": {
        "content": "## Coolie: alert Slack when a lead replies\n\nWhen a lead answers one of your Coolie campaigns, by email or on LinkedIn, or asks it to stop, this workflow posts the reply to a Slack channel.\n\n### How it works\n1. **Coolie webhook** receives the **lead.replied** event, raw body kept.\n2. **Verify Coolie signature** checks the `Coolie-Signature` header with your webhook's secret and stops anything else.\n3. **Replies only** lets `lead.replied` events through, and Coolie's test event.\n4. **Post to Slack** sends who answered, at which company, on which channel, and their words.\n\n### Setup\n1. In **Post to Slack**, connect Slack and pick the channel.\n2. Copy the **Production URL** of **Coolie webhook**.\n3. In Coolie: **Settings → Webhooks**, paste the URL, tick **Lead replied**, click **Add webhook**, then **Reveal** the secret.\n4. Paste the secret at the top of **Verify Coolie signature**.\n5. Save and publish the workflow (on n8n before 2.0, activate it).\n6. Click **Send test** in Coolie: a test message appears in Slack.\n\nOut-of-office answers and bounces are not replies: they never reach this workflow.\nSelf-hosted n8n: allow the crypto module (`NODE_FUNCTION_ALLOW_BUILTIN=crypto`, on the task runners when you use them).\nGuide: https://trycoolie.ai/developers/templates/lead-replied-to-slack",
        "height": 700,
        "width": 480
      },
      "id": "a341ba3b-9d99-4bb6-889d-a04cbbb4d8cc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -280
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "coolie-lead-replied",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "id": "72b85a0f-2c28-4f24-8d2d-c8510ad6f032",
      "name": "Coolie webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "38deca36-b278-432a-8620-1100417e64f3"
    },
    {
      "parameters": {
        "jsCode": "// Coolie signs every event: Coolie-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\">.\n// This node recomputes it on the raw body with your webhook's secret, and stops anything else.\n// Paste the secret below: Coolie > Settings > Webhooks > Reveal (it starts with whsec_).\nconst SECRET = 'PASTE_YOUR_COOLIE_WEBHOOK_SECRET';\n\nconst crypto = require('crypto');\nif (!SECRET.startsWith('whsec_')) {\n  throw new Error('Paste your Coolie webhook secret (whsec_...) in the Verify Coolie signature node.');\n}\nconst request = $input.first().json;\nconst rawBody = (await this.helpers.getBinaryDataBuffer(0, 'data')).toString('utf8');\nconst parts = Object.fromEntries(\n  String(request.headers['coolie-signature'] || '').split(',').map((part) => part.split('=')),\n);\nconst expected = Buffer.from(crypto.createHmac('sha256', SECRET).update(parts.t + '.' + rawBody).digest('hex'));\nconst given = Buffer.from(String(parts.v1 || ''));\nconst fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;\nif (!fresh || given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {\n  throw new Error('Not a Coolie event: the signature does not match.');\n}\nreturn [{ json: JSON.parse(rawBody), pairedItem: { item: 0 } }];"
      },
      "id": "b44bcfef-1a74-479a-86fa-e21cd1b52de7",
      "name": "Verify Coolie signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "551a68cb-2f6d-42dc-8e2c-2885b45f56ea",
              "leftValue": "={{ $json.type }}",
              "rightValue": "lead.replied",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "fe1d48a2-ce4b-4707-85db-802e5702a238",
              "leftValue": "={{ $json.type }}",
              "rightValue": "test",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "ef225f27-2093-443f-8d85-70c26706b09a",
      "name": "Replies only",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "text": "=:speech_balloon: *{{ $json.type === 'test' ? 'Test event from Coolie' : ($json.data.reply.kind === 'stop' ? 'A lead asked to stop' : 'A lead replied by ' + $json.data.reply.channel) }}*\n{{ $json.data.lead && $json.data.lead.contact ? $json.data.lead.contact.name + ($json.data.lead.contact.title ? ', ' + $json.data.lead.contact.title : '') : 'Lead' }}{{ $json.data.lead ? ' at ' + $json.data.lead.company.name : '' }}\n{{ $json.data.reply && $json.data.reply.subject ? '*' + $json.data.reply.subject + '*' : '' }}\n{{ $json.data.reply ? ($json.data.reply.text || '(no text)') : $json.data.message }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "d68d1525-f16c-4ab4-8765-28e7e0303d5c",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        720,
        0
      ]
    }
  ],
  "connections": {
    "Coolie webhook": {
      "main": [
        [
          {
            "node": "Verify Coolie signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Coolie signature": {
      "main": [
        [
          {
            "node": "Replies only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Replies only": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}
