n8n and Make template

Coolie + Slack: alert when competitor churn is detected

This n8n workflow receives Coolie's lead.created webhook, checks its signature, keeps the leads whose signal is Competitor Churn, finds the contact's email with one call to Coolie's API, and alerts a Slack channel: the company, the product it stopped using and when, the contact and their email. The lookup costs 0.1 credit when an email is found, and nothing when none is found or when the lead already has one.

Updated · Works with n8n, Make, Slack · Coolie event: lead.created

What the workflow does

  1. Coolie webhook (Webhook)

    Receives Coolie's POST, answers at once, and keeps the raw body, which the signature is computed on.

  2. Verify Coolie signature (Code)

    Recomputes the Coolie-Signature header, an HMAC-SHA256 of the timestamp, a dot and the raw body with your webhook's secret, compares it in constant time, refuses a request older than five minutes, and passes the event on.

  3. Competitor churn only (IF)

    Keeps the lead.created events whose lead's signal.type is competitor-churn, and Coolie's test event while you set it up.

  4. Find the email (HTTP Request)

    Calls POST /api/v1/agents/{agentId}/leads/contact with the lead's runId and prospectId and field email, with your API key. Set never to fail, so a lead without contact or credits still raises the alert.

  5. Lead and email (Edit Fields)

    Puts the lead and the email found side by side.

  6. Alert Slack (Slack)

    Posts the company, the change and its date, the contact, their email and LinkedIn, and the AI score.

Set it up in n8n

  1. Import the workflow

    Download the JSON file below. In n8n, open a new workflow and choose Import from File, or Import from URL with https://trycoolie.ai/templates/n8n/coolie-competitor-churn-to-slack.json, or paste the JSON onto the canvas.

  2. Give it your API key

    Open Find the email and create a Header Auth credential: name Authorization, value Bearer followed by a space and your Coolie API key, from Settings → API keys.

  3. Connect Slack

    Open Alert Slack, create or pick a Slack credential, and choose the channel.

  4. Copy the webhook's address

    Open Coolie webhook and copy its Production URL: the address Coolie will post to.

  5. Add the webhook in Coolie

    In Coolie, open Settings → Webhooks, paste the address, tick New lead (lead.created) and click Add webhook: nothing is sent yet. Then click Reveal and copy the webhook's secret, which starts with whsec_.

  6. Paste the secret

    In n8n, open Verify Coolie signature and paste the secret between the quotes on its first lines. Without it, the node stops every event, with an error that says so.

  7. Publish the workflow

    Save, then publish the workflow (on n8n before version 2.0, switch it to active): its Production URL now answers.

  8. Send a test

    Back in Coolie, click Send test on the webhook: a test alert appears in your channel. Its sample lead belongs to no agent, so the lookup finds no email for it.

coolie-competitor-churn-to-slack.json

An n8n workflow (Webhook, Code, IF and the tools' own nodes), with a note on the canvas that repeats these steps.

Download the JSON file

Show the workflow JSON
coolie-competitor-churn-to-slack.json
{
  "name": "Coolie: competitor churn → Slack alert",
  "nodes": [
    {
      "parameters": {
        "content": "## Coolie: alert Slack when competitor churn is detected\n\nWhen a Coolie agent finds a company that stopped using a competitor's product, this workflow finds the contact's email through Coolie's API and alerts a Slack channel.\n\n### How it works\n1. **Coolie webhook** receives the **lead.created** event, raw body kept.\n2. **Verify Coolie signature** checks the `Coolie-Signature` header with your webhook's secret and stops anything else.\n3. **Competitor churn only** keeps the leads whose signal is `competitor-churn`, and Coolie's test event.\n4. **Find the email** calls `POST /api/v1/agents/{agentId}/leads/contact`: 0.1 credit when an email is found, nothing when none is found or when the lead already has one.\n5. **Lead and email** puts the lead and the email together.\n6. **Alert Slack** sends the alert.\n\n### Setup\n1. In **Find the email**, create a **Header Auth** credential: name `Authorization`, value `Bearer` followed by a space and your Coolie API key (Settings → API keys).\n2. In **Alert Slack**, connect Slack and pick the channel.\n3. Copy the **Production URL** of **Coolie webhook**.\n4. In Coolie: **Settings → Webhooks**, paste the URL, tick **New lead**, click **Add webhook**, then **Reveal** the secret.\n5. Paste the secret at the top of **Verify Coolie signature**.\n6. Save and publish the workflow (on n8n before 2.0, activate it).\n7. Click **Send test** in Coolie: a test alert appears (its sample lead has no email to find).\n\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/competitor-churn-to-slack",
        "height": 820,
        "width": 480
      },
      "id": "cc8c01c6-8049-46a0-8492-c03cdbfba242",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -280
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "coolie-churn-alert",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "id": "36e33f0d-edd3-4c9d-8678-8e28453df67c",
      "name": "Coolie webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "c14719c1-d5aa-4644-8753-858d3ee1799b"
    },
    {
      "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": "c665dce6-bfcf-4b95-8d5f-4e588256758a",
      "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": "fb8b78ce-2d39-4f37-845d-262f1ad7e7cc",
              "leftValue": "={{ $json.type }}/{{ $json.data.lead ? $json.data.lead.signal.type : '' }}",
              "rightValue": "lead.created/competitor-churn",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "3f7ff085-c072-4896-82d1-2f67e242d980",
              "leftValue": "={{ $json.type }}",
              "rightValue": "test",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "96d0af67-8781-4954-8fd6-66be0c0ffd07",
      "name": "Competitor churn only",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://trycoolie.ai/api/v1/agents/{{ $json.data.lead.agentId || 'none' }}/leads/contact",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ runId: $json.data.lead.runId, prospectId: $json.data.lead.prospectId, field: 'email' }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "6399f3b8-df93-458b-808e-4090d4338e77",
      "name": "Find the email",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "dfdaed7c-c2ba-46d8-8726-62f5af40534b",
              "name": "lead",
              "value": "={{ $('Competitor churn only').item.json.data.lead }}",
              "type": "object"
            },
            {
              "id": "5ffb8939-3d50-4d34-86af-7b4c4e360fe7",
              "name": "test",
              "value": "={{ $('Competitor churn only').item.json.type === 'test' }}",
              "type": "boolean"
            },
            {
              "id": "af99158f-5626-4512-8394-4097eb19717d",
              "name": "email",
              "value": "={{ $json.found === true ? $json.value : '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "f0cd74b2-c4d9-48bd-843c-344f636ee322",
      "name": "Lead and email",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        960,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "text": "=:rotating_light: *Competitor churn{{ $json.test ? ' (test event)' : '' }}: {{ $json.lead.company.name }}*{{ $json.lead.company.domain ? ' · ' + $json.lead.company.domain : '' }}\n{{ $json.lead.signal.headline || $json.lead.signal.name }}{{ $json.lead.signal.postedAt ? ' on ' + $json.lead.signal.postedAt : '' }}\nContact: {{ $json.lead.contact ? $json.lead.contact.name + ($json.lead.contact.title ? ', ' + $json.lead.contact.title : '') : 'none found yet' }}\nEmail: {{ $json.email || 'not found' }}{{ $json.lead.contact && $json.lead.contact.linkedinUrl ? ' · ' + $json.lead.contact.linkedinUrl : '' }}\nAI score: {{ $json.lead.aiScore }}/10",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "30d0643a-c238-46a5-8932-18fb5a72d0c0",
      "name": "Alert Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1200,
        0
      ]
    }
  ],
  "connections": {
    "Coolie webhook": {
      "main": [
        [
          {
            "node": "Verify Coolie signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Coolie signature": {
      "main": [
        [
          {
            "node": "Competitor churn only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Competitor churn only": {
      "main": [
        [
          {
            "node": "Find the email",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Find the email": {
      "main": [
        [
          {
            "node": "Lead and email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead and email": {
      "main": [
        [
          {
            "node": "Alert Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}

Build it in Make

Make's scenario files are its own, so here is the same flow, module by module, in a new scenario.

  1. Create the webhook in Make

    In a new scenario, add Webhooks › Custom webhook, click Add, name it (Coolie leads) and copy its address. Make then waits for a first request to learn the data's structure.

  2. Send Coolie's test event

    In Coolie, open Settings → Webhooks, paste the address, tick New lead (lead.created), click Add webhook, then Send test. Make shows that it determined the structure from the test event, which carries a sample lead in the real shape.

  3. Filter on competitor churn

    Add the next module, and on the link set up a filter: type Equal to lead.created, AND data › lead › signal › type Equal to competitor-churn.

  4. Find the email with HTTP

    Add HTTP › Make a request: method POST, address https://trycoolie.ai/api/v1/agents/{agentId}/leads/contact with data › lead › agentId in place of {agentId}, a header Authorization with the value Bearer and your Coolie API key, and a JSON body with runId and prospectId mapped from data › lead and field set to email. Parse the response, and do not treat a 4xx answer as an error, so a lead without contact or credits still raises the alert.

  5. Add the Slack message

    Add Slack › Create a Message, choose the channel, and write the text from data › lead › company › name, signal › headline and postedAt, contact › name, and the HTTP module's value, the email found.

  6. Turn the scenario on

    Save, and switch the scenario on, set to run immediately as data arrives.

  7. Optional: check Coolie's signature

    A Make webhook address is long and random: keep it private. To refuse anything Coolie did not sign, turn on Get request headers and JSON pass-through in the webhook's advanced settings, parse the text it passes with JSON › Parse JSON, and add a filter comparing the v1 part of the Coolie-Signature header with Make's sha256 function given your secret as its key (it then computes an HMAC, in hex), over the t part of the header, a dot and the raw body.

A competitor-churn event

The lead.created event of a lead whose signal is Competitor Churn: signal.headline names the change, signal.postedAt its date. To map fields in Make before the first one, paste this into its data structure generator. The values are examples.

JSON
{
  "id": "evt_Bv9kR3nX6pQ1sLzM",
  "type": "lead.created",
  "createdAt": "2026-10-19T08:41:56.310Z",
  "data": {
    "lead": {
      "runId": "ui-7c03aa19",
      "prospectId": "p-9e21f4b6c8d0",
      "agentId": "agent-3f2a91c0",
      "status": "enriched",
      "processedAt": "2026-10-19T08:41:55.204Z",
      "icpMatch": true,
      "aiScore": 9.1,
      "feedback": null,
      "signal": {
        "type": "competitor-churn",
        "name": "Competitor Churn",
        "headline": "Stopped using Acme CRM",
        "detail": null,
        "url": null,
        "postedAt": "2026-09-28"
      },
      "company": {
        "name": "Halden Freight",
        "domain": "halden.example",
        "website": "https://halden.example",
        "linkedinUrl": "https://www.linkedin.com/company/example",
        "industry": "Supply Chain and Logistics",
        "employees": 200,
        "country": "Germany",
        "locations": []
      },
      "contact": {
        "name": "Jonas Weber",
        "title": "VP of Sales",
        "seniority": "vp",
        "linkedinUrl": "https://www.linkedin.com/in/example",
        "country": "Germany",
        "email": null,
        "emailConfidence": null,
        "phone": null
      },
      "research": null,
      "sequence": null
    }
  }
}

Frequently asked questions

What counts as competitor churn?

A company that cancelled, downgraded or stopped using a product you named in the agent's Competitor Churn signal, in the last 90 days. See the Competitor Churn signal.

What does the email lookup cost?

0.1 credit when an email is found. Nothing when none is found, and nothing when the lead already has one: the call then returns it as it is.

What happens when my credits run out?

The lookup answers no_credits, the workflow carries on, and the alert goes out without the email.

Can I alert on competitor renewals too?

Yes: add a condition to Competitor churn only, with lead.created/competitor-renewal beside lead.created/competitor-churn.

Does Coolie have a Slack app?

No. This template connects the two through Coolie's signed webhooks: Coolie posts each event to n8n or Make, which posts to Slack. The same events can feed any tool n8n or Make reaches.

My self-hosted n8n refuses require('crypto'). What do I do?

The Code node that checks the signature needs Node's crypto module. n8n Cloud allows it; on a self-hosted n8n, set NODE_FUNCTION_ALLOW_BUILTIN=crypto (on the task runners when you use them) and restart.

How the events are signed and retried: receive Coolie webhooks and verify their signature. Every template: Coolie workflow templates.

Your next conversation is already happening somewhere.

Set up an agent in five minutes. The first leads arrive with their reason to write.