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 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
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
Automatically enriches every new LinkedIn lead added to your Airtable base with AI-structured data and notifies your team in Slack. Airtable Trigger polls for new leads every minute BrightData scrapes
A scheduled process aggregates content from eight distinct data sources and standardizes all inputs into a unified format. AI models perform sentiment scoring, detect conspiracy or misinformation sign