Updated · Works with n8n, Make, Slack · Coolie event: lead.created
What the workflow does
Coolie webhook (Webhook)
Receives Coolie's POST, answers at once, and keeps the raw body, which the signature is computed on.
Verify Coolie signature (Code)
Recomputes the
Coolie-Signatureheader, 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.Competitor churn only (IF)
Keeps the
lead.createdevents whose lead'ssignal.typeiscompetitor-churn, and Coolie's test event while you set it up.Find the email (HTTP Request)
Calls
POST /api/v1/agents/{agentId}/leads/contactwith the lead'srunIdandprospectIdandfieldemail, with your API key. Set never to fail, so a lead without contact or credits still raises the alert.Lead and email (Edit Fields)
Puts the lead and the email found side by side.
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
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.
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.
Connect Slack
Open Alert Slack, create or pick a Slack credential, and choose the channel.
Copy the webhook's address
Open Coolie webhook and copy its Production URL: the address Coolie will post to.
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 withwhsec_.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.
Publish the workflow
Save, then publish the workflow (on n8n before version 2.0, switch it to active): its Production URL now answers.
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.
Show the workflow 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.
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.
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.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.
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.
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.
Turn the scenario on
Save, and switch the scenario on, set to run immediately as data arrives.
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
v1part of theCoolie-Signatureheader with Make'ssha256function given your secret as its key (it then computes an HMAC, in hex), over thetpart 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.
{
"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.
