Updated · Works with n8n, Make, Google Sheets · 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.New leads only (IF)
Lets
lead.createdevents through, and Coolie's test event while you set it up.Lead to columns (Edit Fields)
Turns the lead into one field per column, named as the sheet's header row.
Add to Google Sheets (Google Sheets)
Appends the row, matching the fields to the columns by name.
Set it up in n8n
Create the sheet
In Google Sheets, write these column names in the first row: Found at, Company, Domain, Industry, Employees, Country, Signal, Signal detail, Signal link, Contact, Title, Email, Phone, LinkedIn, AI score, Lead id.
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-new-lead-to-google-sheets.json, or paste the JSON onto the canvas.
Connect Google Sheets
Open Add to Google Sheets, create or pick a Google credential, and choose the document and the sheet.
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 row with the sample lead appears in the sheet.
coolie-new-lead-to-google-sheets.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: new lead → Google Sheets",
"nodes": [
{
"parameters": {
"content": "## Coolie: add every new lead to Google Sheets\n\nEach lead a Coolie agent finds arrives here as a **lead.created** webhook and is appended to a Google Sheet as one row.\n\n### How it works\n1. **Coolie webhook** receives the event, raw body kept.\n2. **Verify Coolie signature** checks the `Coolie-Signature` header with your webhook's secret and stops anything else.\n3. **New leads only** lets `lead.created` events through, and Coolie's test event.\n4. **Lead to columns** turns the lead into the sheet's columns.\n5. **Add to Google Sheets** appends the row, matching the columns by name.\n\n### Setup\n1. Create a sheet whose first row holds: Found at, Company, Domain, Industry, Employees, Country, Signal, Signal detail, Signal link, Contact, Title, Email, Phone, LinkedIn, AI score, Lead id.\n2. In **Add to Google Sheets**, connect Google and pick the document and the sheet.\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 sample row appears.\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/new-lead-to-google-sheets",
"height": 820,
"width": 480
},
"id": "0ce3de92-ef3f-49ba-8c76-8ed36aabe344",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-560,
-280
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "coolie-new-lead-sheets",
"responseMode": "onReceived",
"options": {
"rawBody": true
}
},
"id": "8c3420a6-ed47-4b12-8cea-e7d5f28246ca",
"name": "Coolie webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
],
"webhookId": "6288a954-7ee2-4cf1-8c53-c96fee7c6dec"
},
{
"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": "b12879ba-eb92-400c-8fe6-c1c180e4ca71",
"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": "ed157390-350b-483a-82b6-5a780d2f8943",
"leftValue": "={{ $json.type }}",
"rightValue": "lead.created",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "d0094d9b-f5de-406f-8b36-8631b49813ac",
"leftValue": "={{ $json.type }}",
"rightValue": "test",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "023c2f12-f20b-425c-8ded-01a91ebf1582",
"name": "New leads only",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
480,
0
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "d27657ba-86d9-474d-8f9b-a04c40621fb6",
"name": "Found at",
"value": "={{ $json.data.lead.processedAt }}",
"type": "string"
},
{
"id": "e528d0c3-f835-4e16-86d2-5e067f662d06",
"name": "Company",
"value": "={{ $json.data.lead.company.name }}",
"type": "string"
},
{
"id": "60b2a4ec-f03d-4409-844c-fdb40a65de8e",
"name": "Domain",
"value": "={{ $json.data.lead.company.domain || '' }}",
"type": "string"
},
{
"id": "05e521b6-0931-43e1-853a-300939bdef16",
"name": "Industry",
"value": "={{ $json.data.lead.company.industry || '' }}",
"type": "string"
},
{
"id": "4a5edc3e-377e-49e1-89dd-f45a48a0ba2d",
"name": "Employees",
"value": "={{ $json.data.lead.company.employees === null ? '' : String($json.data.lead.company.employees) }}",
"type": "string"
},
{
"id": "836791ea-6d45-4f84-811d-f1c96a9272dd",
"name": "Country",
"value": "={{ $json.data.lead.company.country || '' }}",
"type": "string"
},
{
"id": "05a4a5a1-5c10-4553-8e1c-113e2e794935",
"name": "Signal",
"value": "={{ $json.data.lead.signal.name }}",
"type": "string"
},
{
"id": "4cab6e7f-d114-40b8-8585-f10aff349bc8",
"name": "Signal detail",
"value": "={{ $json.data.lead.signal.headline || '' }}",
"type": "string"
},
{
"id": "82b4489a-873d-4c33-8b79-7902ca5773de",
"name": "Signal link",
"value": "={{ $json.data.lead.signal.url || '' }}",
"type": "string"
},
{
"id": "89801545-00b9-4825-8c08-8ff3f1aefd31",
"name": "Contact",
"value": "={{ $json.data.lead.contact ? $json.data.lead.contact.name : '' }}",
"type": "string"
},
{
"id": "48df9ff5-7388-4e9d-84a1-947d64cc654e",
"name": "Title",
"value": "={{ $json.data.lead.contact && $json.data.lead.contact.title ? $json.data.lead.contact.title : '' }}",
"type": "string"
},
{
"id": "03f097ff-0a5b-4063-8446-37ac14656a6a",
"name": "Email",
"value": "={{ $json.data.lead.contact && $json.data.lead.contact.email ? $json.data.lead.contact.email : '' }}",
"type": "string"
},
{
"id": "995d1178-2a32-4782-852a-3c91dab5b272",
"name": "Phone",
"value": "={{ $json.data.lead.contact && $json.data.lead.contact.phone ? $json.data.lead.contact.phone : '' }}",
"type": "string"
},
{
"id": "a3bd0dd7-d8a2-418e-8acc-b2b0a6582683",
"name": "LinkedIn",
"value": "={{ $json.data.lead.contact && $json.data.lead.contact.linkedinUrl ? $json.data.lead.contact.linkedinUrl : '' }}",
"type": "string"
},
{
"id": "895560d7-e60f-4022-8740-afc99ac4400a",
"name": "AI score",
"value": "={{ $json.data.lead.aiScore }}",
"type": "number"
},
{
"id": "3f6612cf-17ae-4b9f-802c-0e7b3af76503",
"name": "Lead id",
"value": "={{ $json.data.lead.runId + ':' + $json.data.lead.prospectId }}",
"type": "string"
}
]
},
"options": {}
},
"id": "e57549da-11bf-4b27-8d82-d67f43260413",
"name": "Lead to columns",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
720,
0
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"columns": {
"mappingMode": "autoMapInputData",
"value": {},
"matchingColumns": [],
"schema": []
},
"options": {}
},
"id": "9471ea6a-95b5-4a5b-89f4-d496c0a582ac",
"name": "Add to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
960,
0
]
}
],
"connections": {
"Coolie webhook": {
"main": [
[
{
"node": "Verify Coolie signature",
"type": "main",
"index": 0
}
]
]
},
"Verify Coolie signature": {
"main": [
[
{
"node": "New leads only",
"type": "main",
"index": 0
}
]
]
},
"New leads only": {
"main": [
[
{
"node": "Lead to columns",
"type": "main",
"index": 0
}
],
[]
]
},
"Lead to columns": {
"main": [
[
{
"node": "Add to Google Sheets",
"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.Add the row
Add Google Sheets › Add a Row, connect Google, choose the spreadsheet and the sheet (its first row holds the column names), and map each column: data › lead › processedAt, company › name, domain, industry, employees and country, signal › name, headline and url, contact › name, title, email, phone and linkedinUrl, aiScore, and runId and prospectId for the lead's id.
Filter on new leads
On the link between the two modules, set up a filter: type Equal to lead.created. While you try it, add an OR condition, type Equal to test.
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.
The sheet's columns
Each column is one field of the lead (the event's data.lead):
- Found at:
processedAt, when the agent found the lead. - Company, Domain, Industry, Employees, Country:
company.name,domain,industry,employees,country. - Signal, Signal detail, Signal link:
signal.name,signal.headline(the job title or the change),signal.url. - Contact, Title, Email, Phone, LinkedIn:
contact.name,title,email,phone,linkedinUrl, empty when not found. - AI score:
aiScore, from 0 to 10. - Lead id:
runId:prospectId, the pair every API call on the lead takes.
The event your workflow receives
A lead.created event, as Coolie posts it; contact is null when nobody at the company matches the agent's job titles. To map fields in Make before the first event, paste this into its data structure generator. The values are examples.
{
"id": "evt_Tq4mZ8cW2xLp7VbN",
"type": "lead.created",
"createdAt": "2026-10-19T09:02:14.020Z",
"data": {
"lead": {
"runId": "ui-5b9e2c41",
"prospectId": "p-41b7c2d9e0aa",
"agentId": "agent-3f2a91c0",
"status": "enriched",
"processedAt": "2026-10-19T09:02:13.511Z",
"icpMatch": true,
"aiScore": 8.4,
"feedback": null,
"signal": {
"type": "hiring",
"name": "Hiring",
"headline": "Operations Manager",
"detail": null,
"url": "https://jobs.example.com/12345",
"postedAt": "2026-10-17"
},
"company": {
"name": "Northwind Health",
"domain": "northwind.example",
"website": "https://northwind.example",
"linkedinUrl": "https://www.linkedin.com/company/example",
"industry": "Hospitals and Health Care",
"employees": 320,
"country": "France",
"locations": [
"Lyon, France"
]
},
"contact": {
"name": "Camille Martin",
"title": "COO",
"seniority": "c_suite",
"linkedinUrl": "https://www.linkedin.com/in/example",
"country": "France",
"email": "camille.martin@northwind.example",
"emailConfidence": 0.92,
"phone": null
},
"research": null,
"sequence": null
}
}
}Frequently asked questions
Can I add or remove columns?
Yes: change the sheet's first row and the fields of Lead to columns together, under the same names. The event's fields are in the sample below.
Can a lead be added twice?
Coolie tries an event again only when your workflow did not answer with a 2xx. The Lead id column names each lead if you ever need to remove a duplicate.
Can I fill in the emails the agent did not find?
Yes, with one API call before the sheet: see how to find a lead's email, as the competitor churn alert does.
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.
What does it cost on Coolie's side?
Nothing more: webhooks are part of every plan, and the leads are those your agents find. n8n, Make and Slack bill as they usually do.
How the events are signed and retried: receive Coolie webhooks and verify their signature. Every template: Coolie workflow templates.
