This workflow corresponds to n8n.io template #15533 — we link there as the canonical source.
This workflow follows the Airtable → OpenAI recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "zmRpMXO0azBftOGa",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Hiring Signal Tracker powered by Bright Data",
"tags": [],
"nodes": [
{
"id": "21585fe6-d444-4383-9d58-6c1f19d51624",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2960,
624
],
"parameters": {
"color": 7,
"width": 540,
"height": 820,
"content": "# \ud83c\udfaf AI Hiring Signal Tracker powered by Bright Data\n\nTurn LinkedIn job postings into qualified pipeline. Detect when target companies are hiring sales/eng/leadership roles, score the signal with AI, draft personalized outreach, and sync to HubSpot + Slack.\n\n## How it works\n\n1. **Schedule Trigger** runs every morning\n2. **Airtable** returns the active LinkedIn job-search URLs to monitor\n3. **Bright Data Web Unlocker** scrapes each search results page\n4. **GPT extracts** structured job data from the raw HTML (title, company, location, etc.)\n5. **GPT scores** each job as a HIGH / MEDIUM / LOW buying signal and drafts a cold outreach email\n6. **HubSpot** creates a deal for HIGH/MEDIUM signals\n7. **Slack** alerts the sales team with the rationale and suggested email\n\n## Why Bright Data?\n\n- Scrapes LinkedIn search pages reliably (handles JS, anti-bot, geo)\n- Returns clean Markdown ready for LLM parsing\n- Scales from 1 to thousands of searches per day\n\n## Prerequisites\n\n- A [Bright Data](https://brightdata.com) account with a Web Unlocker zone\n- An [OpenAI](https://platform.openai.com) API key\n- An [Airtable](https://airtable.com) base with `Searches` table (`LinkedIn URL`, `Active`, `Last Run`)\n- A [HubSpot](https://hubspot.com) account\n- A [Slack](https://slack.com) workspace\n\n## Setup (~10 min)\n\n1. Connect Airtable, Bright Data, OpenAI, HubSpot and Slack credentials\n2. Add LinkedIn job-search URLs to Airtable with `Active = TRUE`\n3. Pick your Slack channel in the notification node\n4. Activate the workflow"
},
"typeVersion": 1
},
{
"id": "de30580e-b7ad-4f20-90f8-3f2eabb33c39",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3680,
1072
],
"parameters": {
"color": 4,
"width": 620,
"height": 376,
"content": "## \ud83d\ude80 1. Trigger & Get Searches\n\nThe **Schedule Trigger** kicks off daily at 9am. **Airtable** returns the active LinkedIn search URLs (sorted by `Last Run` so the oldest gets refreshed first), and the **loop** processes them one by one.\n\n### \ud83d\udd04 Swap-friendly\n- Trigger \u2192 Webhook, Cron, Form, Chat\n- CRM \u2192 HubSpot, Notion, Sheets, Postgres\u2026"
},
"typeVersion": 1
},
{
"id": "5032ad49-6baf-4cde-b007-19410673f52e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4368,
992
],
"parameters": {
"color": 3,
"width": 720,
"height": 460,
"content": "## \ud83c\udf10 2. Scrape & Parse Jobs\n\n**Bright Data Web Unlocker** fetches the LinkedIn search page as clean Markdown. **GPT-5.4-mini** then extracts a structured list of up to 30 jobs (title, company, location, posted date, URL, early applicant flag\u2026). The **JS code** safely parses the JSON, even when the model truncates its output.\n\n- Bright Data handles JS, CAPTCHAs, geo-blocks\n- The LLM does the structured parsing \u2014 no fragile CSS selectors\n- \ud83d\udd04 Swap GPT for any LLM (Claude, Gemini, Mistral, Groq\u2026)"
},
"typeVersion": 1
},
{
"id": "f8983555-290b-4351-8d18-2a84a4bc15b8",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
5168,
1008
],
"parameters": {
"color": 5,
"width": 300,
"height": 440,
"content": "## \ud83e\udde0 3. AI Signal Analysis\n\nFor each job, **GPT-4o-mini** returns:\n- `signal_strength` (HIGH / MEDIUM / LOW)\n- `rationale` (1 sentence)\n- `outreach_email` (3-4 sentence draft)\n\nUses a strict JSON schema so the output is always valid.\n\n\ud83d\udd04 Swap for any LLM."
},
"typeVersion": 1
},
{
"id": "77c208cc-eee7-445f-a57d-67e6ac143224",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
5552,
992
],
"parameters": {
"color": 6,
"width": 720,
"height": 696,
"content": "## \ud83c\udfaf 4. Filter, Sync & Notify\n\n1. **IF** keeps only HIGH or MEDIUM signals (LOW signals are skipped)\n2. **HubSpot** creates a deal with the rationale and the suggested email\n3. **Slack** alerts the sales team with the full context and the HubSpot deal link\n\n### \ud83d\udd04 Swap-friendly\n- CRM \u2192 Salesforce, Pipedrive, Attio, Close, Notion\u2026\n- Channel \u2192 Email, Discord, Teams, Telegram, SMS\u2026"
},
"typeVersion": 1
},
{
"id": "326c692a-e135-4fc3-aebf-671f260caf24",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3728,
1296
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.3
},
{
"id": "637868ad-9865-48a8-86f9-7cef8c3266c8",
"name": "Get Active Searches",
"type": "n8n-nodes-base.airtable",
"position": [
3936,
1296
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "YOUR_AIRTABLE_BASE_ID",
"cachedResultUrl": "",
"cachedResultName": "Hiring Signal Tracker"
},
"sort": {
"property": [
{
"field": "Last Run"
}
]
},
"table": {
"__rl": true,
"mode": "list",
"value": "YOUR_AIRTABLE_TABLE_ID",
"cachedResultUrl": "",
"cachedResultName": "Searches"
},
"options": {},
"operation": "search",
"filterByFormula": "{Active} = TRUE()"
},
"typeVersion": 2.2
},
{
"id": "624a87ac-880f-4704-9504-2528a3f6d52f",
"name": "Loop Over Searches",
"type": "n8n-nodes-base.splitInBatches",
"position": [
4144,
1296
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "eab6ebf8-2470-4bec-82a5-d5e973d9cfd9",
"name": "Scrape LinkedIn with Bright Data",
"type": "@brightdata/n8n-nodes-brightdata.brightData",
"position": [
4432,
1312
],
"parameters": {
"url": "={{ $json.fields['LinkedIn URL'] }}",
"zone": {
"__rl": true,
"mode": "list",
"value": "n8n_unlocker"
},
"country": {
"__rl": true,
"mode": "list",
"value": "us"
},
"data_format": "markdown",
"requestOptions": {}
},
"typeVersion": 1
},
{
"id": "cb20e4bf-d04b-4613-8ce1-ea2e18cf24bc",
"name": "Extract Jobs (AI)",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
4624,
1312
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-5.4-mini",
"cachedResultName": "GPT-5.4-MINI"
},
"options": {},
"responses": {
"values": [
{
"role": "system",
"content": "You are a LinkedIn job posting parser. Extract structured data from raw LinkedIn jobs HTML.\n\nOutput ONLY valid JSON, no markdown, no preamble, no explanations.\n\nFormat:\n{\n \"jobs\": [\n {\n \"title\": \"Account Executive\",\n \"company\": \"Acme Corp\",\n \"company_linkedin_slug\": \"acme-corp\",\n \"location\": \"San Francisco, CA\",\n \"posted_date\": \"2 hours ago\",\n \"posted_iso\": \"2026-05-05\",\n \"job_url\": \"https://www.linkedin.com/jobs/view/123456\",\n \"early_applicant\": true,\n \"actively_hiring\": true\n }\n ]\n}\n\nExtract from <li><div class=\"base-card ...\">...</div></li> blocks.\n- title: <h3 class=\"base-search-card__title\">\n- company: <h4 class=\"base-search-card__subtitle\"><a>...</a>\n- company_linkedin_slug: from the company link href, extract slug after /company/\n- location: <span class=\"job-search-card__location\">\n- posted_date: text inside <time>\n- posted_iso: from <time datetime=\"YYYY-MM-DD\">\n- job_url: from <a class=\"base-card__full-link\" href=\"...\">\n- early_applicant: true if \"Be an early applicant\" appears in the card\n- actively_hiring: true if \"Actively Hiring\" appears in the card\n\nLimit to first 30 jobs. If no jobs found, return {\"jobs\": []}."
},
{
"content": "=Extract jobs from this LinkedIn HTML chunk:\n\n{{ $json }}"
}
]
},
"builtInTools": {}
},
"typeVersion": 2.1
},
{
"id": "abc96954-7c82-4f96-ade8-2b63b7bac20b",
"name": "Parse JSON Output",
"type": "n8n-nodes-base.code",
"position": [
4976,
1312
],
"parameters": {
"jsCode": "let text = $input.item.json.output[0].content[0].text;\n\n// Strip markdown backticks if present\ntext = text.replace(/```json\\s*/g, '').replace(/```\\s*/g, '').trim();\n\n// Try normal parse first\ntry {\n const parsed = JSON.parse(text);\n return { json: parsed };\n} catch (err) {\n // If parse fails, try to recover what we can\n // by finding the last \"}\" that closes a complete job object\n const jobsStart = text.indexOf('\"jobs\":[');\n if (jobsStart === -1) {\n throw new Error('No jobs array found in response');\n }\n \n let lastValidEnd = -1;\n let depth = 0;\n let inString = false;\n let escape = false;\n \n for (let i = jobsStart + 8; i < text.length; i++) {\n const c = text[i];\n if (escape) { escape = false; continue; }\n if (c === '\\\\') { escape = true; continue; }\n if (c === '\"') { inString = !inString; continue; }\n if (inString) continue;\n if (c === '{') depth++;\n if (c === '}') {\n depth--;\n if (depth === 0) lastValidEnd = i;\n }\n }\n \n if (lastValidEnd === -1) {\n throw new Error('Could not recover any complete jobs');\n }\n \n // Rebuild a valid truncated JSON\n const truncated = text.substring(0, lastValidEnd + 1) + ']}';\n const parsed = JSON.parse(truncated);\n return { json: parsed };\n}"
},
"typeVersion": 2
},
{
"id": "448dd4f2-76b6-4c22-a2f3-bcc4562bc97d",
"name": "Score Signal & Draft Email",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
5200,
1312
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"textFormat": {
"textOptions": {
"type": "json_schema",
"schema": "{\n \"type\": \"object\",\n \"properties\": {\n \"signal_strength\": {\n \"type\": \"string\",\n \"enum\": [\"HIGH\", \"MEDIUM\", \"LOW\"]\n },\n \"rationale\": {\n \"type\": \"string\"\n },\n \"outreach_email\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false,\n \"required\": [\"signal_strength\", \"rationale\", \"outreach_email\"]\n}"
}
}
},
"responses": {
"values": [
{
"role": "system",
"content": "You are a B2B sales buying signal analyzer + outreach copywriter.\n\nGiven a job posting, classify if it's a buying signal AND draft a personalized cold outreach email if relevant.\n\nOutput ONLY valid JSON with this exact structure:\n\n{\n \"signal_strength\": \"HIGH\" | \"MEDIUM\" | \"LOW\",\n \"rationale\": \"1 sentence explaining why\",\n \"outreach_email\": \"3-4 sentence cold email draft (only if HIGH or MEDIUM, otherwise empty string)\"\n}\n\nHIGH signals: Senior leadership hire (Head of, VP, Founding role), multiple roles open at same company, net-new sales hire at B2B SaaS.\n\nMEDIUM signals: Mid-level B2B sales/eng/marketing roles at growing companies.\n\nLOW signals: Junior IC roles, sports teams, retail/hospitality, agency staffing, geographic-specific sales (Tampa AE, etc.). Set outreach_email to \"\" for LOW signals.\n\nEmail style: 3-4 sentences max. Reference the specific hiring signal. Soft CTA (book 15 min). No \"I hope this finds you well\". First-person from the rep."
},
{
"content": "=Title: {{ $json.jobs[0].title }}\nCompany: {{ $json.jobs[0].company }}\nLocation: {{ $json.jobs[0].location }}\nPosted: {{ $json.jobs[0].posted_date }}\nURL: {{ $json.jobs[0].job_url }}\n\nAnalyze and respond with JSON."
}
]
},
"builtInTools": {}
},
"typeVersion": 2.1
},
{
"id": "cb0f575c-95eb-46c3-bb77-364f1031a4f6",
"name": "HIGH or MEDIUM Signal?",
"type": "n8n-nodes-base.if",
"position": [
5568,
1312
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "2bd376ae-3438-41c7-8b8b-cc6fa7bf36a5",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.output[0].content[0].text.signal_strength }}",
"rightValue": "HIGH"
},
{
"id": "bc249f13-0a20-4e22-9cfc-a671095202f4",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.output[0].content[0].text.signal_strength }}",
"rightValue": "MEDIUM"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "295f54c4-8e0a-40cb-9439-79e81bf315d6",
"name": "Create HubSpot Deal",
"type": "n8n-nodes-base.hubspot",
"position": [
5856,
1408
],
"parameters": {
"stage": "appointmentscheduled",
"resource": "deal",
"authentication": "oAuth2",
"additionalFields": {
"dealName": "=Hiring signal \u2014 {{ $('Parse JSON Output').item.json.jobs[0].company }} hiring {{ $('Parse JSON Output').item.json.jobs[0].title }}",
"description": "={{ $json.output[0].content[0].text.rationale }}\n\nSuggested email:\n{{ $json.output[0].content[0].text.outreach_email }}\n\nJob: {{ $('Parse JSON Output').item.json.jobs[0].title }}\nLocation: {{ $('Parse JSON Output').item.json.jobs[0].location }}"
}
},
"typeVersion": 2.2
},
{
"id": "a3c3a68d-7e41-471c-baf1-139f98563d17",
"name": "Notify Slack",
"type": "n8n-nodes-base.slack",
"position": [
6128,
1488
],
"parameters": {
"text": "=\ud83c\udfaf *New buying signal*\n\n\ud83d\udccb *{{ $('Parse JSON Output').item.json.jobs[0].title }}* at *{{ $('Parse JSON Output').item.json.jobs[0].company }}*\n\ud83d\udccd {{ $('Parse JSON Output').item.json.jobs[0].location }}\n\ud83d\udd25 Signal: *{{ $('Score Signal & Draft Email').item.json.output[0].content[0].text.signal_strength }}*\n\n\ud83d\udca1 *Why this matters:*\n{{ $('Score Signal & Draft Email').item.json.output[0].content[0].text.rationale }}\n\n\ud83d\udce7 *Suggested outreach:*\n{{ $('Score Signal & Draft Email').item.json.output[0].content[0].text.outreach_email }}\n\n\ud83d\udd17 <{{ $('Parse JSON Output').item.json.jobs[0].job_url }}|View job posting>\n\ud83d\udcbc HubSpot deal ID: {{ $json.dealId }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "leads"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.4
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "b34ed4df-956c-4e29-82c4-38dbc8014235",
"connections": {
"Notify Slack": {
"main": [
[
{
"node": "Loop Over Searches",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get Active Searches",
"type": "main",
"index": 0
}
]
]
},
"Extract Jobs (AI)": {
"main": [
[
{
"node": "Parse JSON Output",
"type": "main",
"index": 0
}
]
]
},
"Parse JSON Output": {
"main": [
[
{
"node": "Score Signal & Draft Email",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Searches": {
"main": [
[],
[
{
"node": "Scrape LinkedIn with Bright Data",
"type": "main",
"index": 0
}
]
]
},
"Create HubSpot Deal": {
"main": [
[
{
"node": "Notify Slack",
"type": "main",
"index": 0
}
]
]
},
"Get Active Searches": {
"main": [
[
{
"node": "Loop Over Searches",
"type": "main",
"index": 0
}
]
]
},
"HIGH or MEDIUM Signal?": {
"main": [
[
{
"node": "Create HubSpot Deal",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Searches",
"type": "main",
"index": 0
}
]
]
},
"Score Signal & Draft Email": {
"main": [
[
{
"node": "HIGH or MEDIUM Signal?",
"type": "main",
"index": 0
}
]
]
},
"Scrape LinkedIn with Bright Data": {
"main": [
[
{
"node": "Extract Jobs (AI)",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
A daily Schedule Trigger pulls active LinkedIn job-search URLs from Airtable Bright Data Web Unlocker scrapes each search results page GPT extracts structured job data from the raw HTML A second GPT call scores each job as a HIGH / MEDIUM / LOW buying signal and drafts a…
Source: https://n8n.io/workflows/15533/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow runs every weekday morning to find HubSpot deals with no recent activity, uses OpenAI to generate a personalized re-engagement email, sends it via Gmail, logs the outcome to Google Sheet
This workflow runs on a daily schedule to analyze all Closed–Lost deals from your CRM and uncover the true reason behind each loss. It uses AI to classify the primary loss category, generate a confide
This workflow automatically fetches product reviews from your WooCommerce store, analyzes the sentiment of each review using AI, stores the results in Airtable and sends a summary of positive, neutral
Time Tracker - 15min Ping (Template). Uses slack, openAi, airtable, httpRequest. Scheduled trigger; 17 nodes.
A schedule trigger periodically fetches the list of URLs to monitor from your CRM (Airtable by default) Bright Data Web Unlocker scrapes each page (handles JS, CAPTCHAs, geo-blocks) Claude Sonnet 4.6