This workflow corresponds to n8n.io template #18125 — we link there as the canonical source.
This workflow follows the Chainllm → Anthropic Chat 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": "PuYluuZv48Qz4rRX",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Source candidates from job ads posted in Slack with Coresignal and Claude",
"tags": [],
"nodes": [
{
"id": "5f09b87b-3f1b-42ba-8d12-644f4984a611",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4656,
2096
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Source candidates from job ads posted in Slack with Coresignal and Claude\n\n### How it works\n\nThis workflow monitors Slack for newly posted job ads, extracts job URLs, and processes each unique ad in a loop. For each job, it retrieves job details from Coresignal, uses Claude to infer candidate search filters, searches Coresignal\u2019s employee database, and posts a formatted candidate shortlist back to Slack. It also acknowledges each job as it enters processing.\n\n### Setup steps\n\n- Connect and authorize the Slack Trigger and Slack nodes with access to the source channel and the destination channel/thread where acknowledgements and shortlists should be posted.\n- Configure Coresignal credentials/API access for the job search, job enrichment, employee search, and employee retrieval nodes.\n- Configure the Anthropic Chat Model credentials for Claude in the AI filter extraction chain.\n- Set the workflow configuration value `candidates_per_job` to control how many candidates are returned per job ad.\n- Verify the Slack message format contains job URLs that the Extract Job URLs code node can parse.\n\n### Customization\n\nAdjust the URL extraction logic, Claude prompt/schema for search filters, Coresignal employee query builder, or Slack shortlist formatting to match different job boards, candidate criteria, and posting style."
},
"typeVersion": 1
},
{
"id": "2650c7f0-fb67-4a77-b3ba-124f59786b95",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
5152,
2096
],
"parameters": {
"color": 7,
"width": 880,
"height": 336,
"content": "## Capture job ads\n\nListens for Slack messages, sets the candidate count configuration, extracts job posting URLs, and removes duplicates so only new job ads continue."
},
"typeVersion": 1
},
{
"id": "64786ec6-52df-4e37-9711-e9dc13b94947",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
6096,
2128
],
"parameters": {
"color": 7,
"width": 464,
"height": 480,
"content": "## Iterate and acknowledge\n\nLoops through each unique job ad and sends a nearby Slack acknowledgement that processing has started."
},
"typeVersion": 1
},
{
"id": "974640fc-5a08-419f-9f88-c2b7206bfcc1",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
6576,
2144
],
"parameters": {
"color": 7,
"width": 624,
"height": 304,
"content": "## Retrieve job details\n\nSearches Coresignal for the job, selects the relevant job ID from the search response, and fetches the full job record."
},
"typeVersion": 1
},
{
"id": "14f3441e-2ca7-4743-a7e9-2aef716d9b5e",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
7232,
2112
],
"parameters": {
"color": 7,
"width": 608,
"height": 528,
"content": "## Extract candidate filters\n\nUses Claude with a structured output parser to convert the job details into search filters, then builds a Coresignal employee search query from those filters."
},
"typeVersion": 1
},
{
"id": "b76d7521-4b57-4562-a352-dbf1e3d7c5bb",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
7872,
2144
],
"parameters": {
"color": 7,
"width": 624,
"height": 304,
"content": "## Find matching employees\n\nRuns the generated employee search in Coresignal, keeps the top scoring employee IDs, and retrieves the corresponding employee profiles."
},
"typeVersion": 1
},
{
"id": "f5f9fc91-c1db-4653-9eb8-2a4448d65c6b",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
8528,
2096
],
"parameters": {
"color": 7,
"width": 400,
"height": 336,
"content": "## Post candidate shortlist\n\nFormats the retrieved profiles into a Slack-friendly shortlist and posts it back to Slack before returning control to the job loop for the next ad."
},
"typeVersion": 1
},
{
"id": "ae1d2634-00dd-4d49-b8d7-92b5a118dc35",
"name": "When Job Ad Posted on Slack",
"type": "n8n-nodes-base.slackTrigger",
"position": [
5216,
2256
],
"parameters": {
"options": {},
"trigger": [
"message"
],
"authentication": "oAuth2",
"watchWorkspace": true
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5c720d03-a6f2-47b5-8b44-7662707e9cfa",
"name": "Set Job Candidates Limit",
"type": "n8n-nodes-base.set",
"position": [
5440,
2256
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-count",
"name": "candidates_per_job",
"type": "number",
"value": 5
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "2e944429-8559-47c9-ad02-3d1430a0e838",
"name": "Extract Job URL from Message",
"type": "n8n-nodes-base.code",
"position": [
5648,
2256
],
"parameters": {
"jsCode": "// Input is a single Slack message event from the Slack Trigger, with\n// candidates_per_job merged in by Workflow Config.\n// Pull every LinkedIn job URL out of the text and emit one item per job.\n// Returning an empty array is fine \u2014 downstream nodes simply don't run,\n// which is how ordinary channel chatter costs nothing.\n\nconst out = [];\nconst seenThisRun = new Set();\n\nfor (const item of $input.all()) {\n const m = item.json || {};\n\n // Skip anything the bot posted (our own shortlist contains the job URL and\n // would otherwise re-trigger the workflow) and skip joins/edits/etc.\n if (m.bot_id || m.subtype) continue;\n\n let raw = String(m.text || '');\n raw = raw.replace(/<@[^>]+>/g, ' '); // strip @mentions\n raw = raw.replace(/<([^|>]+)(\\|[^>]*)?>/g, '$1'); // unwrap Slack's <url|label>\n\n const urls = raw.match(/https?:\\/\\/[^\\s<>]*linkedin\\.com\\/jobs[^\\s<>]*/gi) || [];\n\n // The trigger gives the channel as a plain ID string (resolveIds off).\n const channelId = typeof m.channel === 'string'\n ? m.channel\n : (m.channel && (m.channel.id || m.channel.value)) || m.channel_id || null;\n\n for (const url of urls) {\n // /jobs/view/4237171755 | /jobs/view/slug-at-company-4237171755 | ?currentJobId=4237171755\n const patterns = [\n /currentJobId=(\\d{6,})/,\n /jobs\\/view\\/(?:[^\\/?#\\s]*?-)?(\\d{6,})/\n ];\n\n let jobId = null;\n for (const p of patterns) {\n const mm = url.match(p);\n if (mm) { jobId = mm[1]; break; }\n }\n if (!jobId || seenThisRun.has(jobId)) continue;\n seenThisRun.add(jobId);\n\n out.push({\n json: {\n linkedin_job_id: jobId,\n job_url: url,\n slack_channel_id: channelId,\n slack_user_id: m.user || null,\n slack_message_ts: m.ts || null,\n slack_thread_ts: m.thread_ts || m.ts || null,\n candidates_per_job: Number(m.candidates_per_job) || 5\n }\n });\n }\n}\n\nreturn out;"
},
"typeVersion": 2
},
{
"id": "274ff877-361f-43bc-b8fd-6cdacdcc38ae",
"name": "Deduplicate Job Ads",
"type": "n8n-nodes-base.removeDuplicates",
"position": [
5872,
2256
],
"parameters": {
"options": {
"historySize": 10000
},
"operation": "removeItemsSeenInPreviousExecutions",
"dedupeValue": "={{ $json.linkedin_job_id }}"
},
"typeVersion": 2
},
{
"id": "9440906f-6b42-4de7-b4d2-fa31350b3955",
"name": "Loop Over Job Listings",
"type": "n8n-nodes-base.splitInBatches",
"position": [
6144,
2256
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "937eae7d-8876-4c49-ab58-a58ffbfd56a4",
"name": "Fetch Job Details from Coresignal",
"type": "n8n-nodes-coresignal-api.coresignal",
"position": [
6624,
2272
],
"parameters": {
"esQuery": "={{ JSON.stringify({ query: { nested: { path: 'job_sources', query: { term: { 'job_sources.source_id': $json.linkedin_job_id } } } } }) }}",
"resource": "job",
"operation": "search_es_dsl"
},
"credentials": {
"coresignalApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ac913546-ee76-43ed-a1c5-c47d8d5267dd",
"name": "Post Acknowledgement on Slack",
"type": "n8n-nodes-base.slack",
"position": [
6416,
2432
],
"parameters": {
"text": "=:hourglass_flowing_sand: <@{{ $json.slack_user_id }}> on it \u2014 pulling job `{{ $json.linkedin_job_id }}` from Coresignal and searching for candidates.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.slack_channel_id }}"
},
"otherOptions": {
"mrkdwn": true,
"unfurl_links": false,
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "4cccff91-0e61-4353-b73b-7e90e892b67e",
"name": "Extract Job ID",
"type": "n8n-nodes-base.code",
"position": [
6848,
2272
],
"parameters": {
"jsCode": "// Coresignal search endpoints return a bare JSON array of IDs. The Coresignal node\n// passes each array element through as its own item, so an ID arrives as a raw\n// number in item.json. Normalise every plausible shape.\nfunction collectIds(items) {\n const ids = [];\n for (const item of items) {\n const j = item.json;\n if (j === null || j === undefined) continue;\n if (typeof j === 'number' || typeof j === 'string') { ids.push(j); continue; }\n if (Array.isArray(j)) { ids.push(...j); continue; }\n if (Array.isArray(j.data)) { ids.push(...j.data); continue; }\n if (j.id !== undefined) { ids.push(j.id); continue; }\n const vals = Object.values(j);\n if (vals.length === 1 && (typeof vals[0] === 'number' || typeof vals[0] === 'string')) ids.push(vals[0]);\n }\n return ids.filter(v => v !== null && v !== undefined && v !== '');\n}\n\nconst ids = collectIds($input.all());\n\n// Batch size is 1, so this is the job currently being processed.\nconst ctx = $('Loop Over Job Listings').first().json;\n\nif (ids.length === 0) {\n throw new Error('Coresignal has no multi-source job matching LinkedIn job ID ' + ctx.linkedin_job_id + '. It may be too new, or not indexed from that source.');\n}\n\nreturn [{ json: { coresignal_job_id: ids[0], match_count: ids.length, ...ctx } }];"
},
"typeVersion": 2
},
{
"id": "45a18997-2a32-4fc3-8f49-b780d21b9005",
"name": "Retrieve Job Data from Coresignal",
"type": "n8n-nodes-coresignal-api.coresignal",
"position": [
7056,
2272
],
"parameters": {
"jobId": "={{ $json.coresignal_job_id }}",
"resource": "job"
},
"credentials": {
"coresignalApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "1782c3e6-b066-4eb2-ae1e-60c49cf53816",
"name": "Claude Model for Language Processing",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
7392,
2480
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "claude-sonnet-4-5",
"cachedResultName": "claude-sonnet-4-5"
},
"options": {
"temperature": 0,
"maxTokensToSample": 2048
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "7481bd4c-9a4e-4d0a-b098-b9aa361200d4",
"name": "Parse AI Output Filters",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
7568,
2496
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"job_titles\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"3-6 realistic current job titles a strong candidate would hold today. Plain titles only.\"\n },\n \"must_have_skills\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"3-8 hard skills, tools or technologies explicitly required by the ad. Single terms, lowercase.\"\n },\n \"nice_to_have_skills\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"0-8 preferred-but-optional skills.\"\n },\n \"countries\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"Full English country names where the candidate should be located, e.g. 'United States', 'Germany'. Empty array if the role is fully remote and location-agnostic.\"\n },\n \"min_years_experience\": {\n \"type\": \"number\",\n \"description\": \"Minimum total years of professional experience. Use 0 if the ad implies entry level.\"\n },\n \"seniority\": {\n \"type\": \"string\",\n \"description\": \"One of: Internship, Entry level, Associate, Mid-Senior level, Director, Executive\"\n },\n \"search_summary\": {\n \"type\": \"string\",\n \"description\": \"One sentence describing the ideal candidate.\"\n }\n },\n \"required\": [\"job_titles\", \"must_have_skills\", \"nice_to_have_skills\", \"countries\", \"min_years_experience\", \"seniority\", \"search_summary\"]\n}"
},
"typeVersion": 1.2
},
{
"id": "1b200e30-a531-4f22-8428-ad5b445c79b6",
"name": "AI Extract Search Criteria",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
7296,
2272
],
"parameters": {
"text": "=You are a technical recruiter turning a job ad into search filters for a people-search database.\n\nJOB AD\nTitle: {{ $json.title }}\nCompany: {{ $json.company_name }}\nLocation: {{ $json.location }} ({{ $json.country }})\nSeniority (as posted): {{ $json.seniority }}\nEmployment type: {{ $json.employment_type }}\nDepartment: {{ $json.department }}\nRemote allowed: {{ $json.accepts_remote }}\n\nDescription:\n{{ String($json.description || '').slice(0, 12000) }}\n\nRULES\n- Base every filter on what the ad actually says. Do not invent requirements.\n- job_titles must be titles people put on their own profile, not the ad's marketing headline.\n- must_have_skills should be searchable single terms (e.g. \"kubernetes\", \"python\", \"salesforce\"), not sentences.\n- If the ad gives no country and is fully remote, return an empty countries array.\n- If the ad states a years-of-experience minimum, use it. Otherwise infer conservatively from seniority.",
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.5
},
{
"id": "ed55a52a-f455-4b20-a9df-f996a7e7e51e",
"name": "Build Employee Search Query",
"type": "n8n-nodes-base.code",
"position": [
7696,
2272
],
"parameters": {
"jsCode": "// Turn the AI-extracted filters into a Coresignal Multi-source Employee ES DSL query.\nconst raw = $input.first().json;\nconst f = raw.output || raw.data || raw;\n\nconst titles = (f.job_titles || []).filter(Boolean).slice(0, 6);\nconst must = (f.must_have_skills || []).filter(Boolean).slice(0, 8);\nconst nice = (f.nice_to_have_skills || []).filter(Boolean).slice(0, 8);\nconst countries = (f.countries || []).filter(Boolean);\nconst minMonths = Math.max(0, Math.round(Number(f.min_years_experience || 0) * 12));\n\nif (titles.length === 0) {\n throw new Error('AI returned no job_titles \u2014 cannot build an employee search.');\n}\n\n// Hard filters\nconst filter = [\n { term: { is_deleted: 0 } },\n { term: { is_working: 1 } }\n];\nif (minMonths > 0) {\n filter.push({ range: { total_experience_duration_months: { gte: minMonths } } });\n}\nif (countries.length > 0) {\n filter.push({ terms: { location_country: countries } });\n}\n\n// At least one title variant must match the person's current role.\nconst titleShould = titles.map(t => ({ match_phrase: { active_experience_title: t } }));\n\n// Skills only boost relevance. Must-haves are weighted higher than nice-to-haves.\nconst skillShould = [];\nfor (const s of must) {\n skillShould.push({ match: { inferred_skills: { query: s, operator: 'and', boost: 3 } } });\n skillShould.push({ match: { historical_skills: { query: s, operator: 'and', boost: 2 } } });\n}\nfor (const s of nice) {\n skillShould.push({ match: { inferred_skills: { query: s, operator: 'and', boost: 1 } } });\n}\n\nconst esQuery = {\n query: {\n bool: {\n filter,\n must: [{ bool: { should: titleShould, minimum_should_match: 1 } }],\n should: skillShould\n }\n },\n sort: ['_score']\n};\n\nconst job = $('Retrieve Job Data from Coresignal').first().json;\nconst ctx = $('Extract Job ID').first().json;\n\nreturn [{\n json: {\n es_query: esQuery,\n filters: f,\n job_title: job.title,\n job_company: job.company_name,\n job_location: job.location,\n job_url: ctx.job_url,\n slack_channel_id: ctx.slack_channel_id,\n slack_user_id: ctx.slack_user_id,\n candidates_per_job: ctx.candidates_per_job || 5\n }\n}];"
},
"typeVersion": 2
},
{
"id": "47822cfa-6177-4979-a543-a56539e59400",
"name": "Search Employees in Coresignal",
"type": "n8n-nodes-coresignal-api.coresignal",
"position": [
7920,
2272
],
"parameters": {
"esQuery": "={{ JSON.stringify($json.es_query) }}",
"operation": "search_es_dsl"
},
"credentials": {
"coresignalApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5377fe45-8e3c-47bd-910f-ee63dbef4826",
"name": "Select Top Employee IDs",
"type": "n8n-nodes-base.code",
"position": [
8128,
2272
],
"parameters": {
"jsCode": "// Normalise the ID array and keep the top N (search returns best-scoring first).\nfunction collectIds(items) {\n const ids = [];\n for (const item of items) {\n const j = item.json;\n if (j === null || j === undefined) continue;\n if (typeof j === 'number' || typeof j === 'string') { ids.push(j); continue; }\n if (Array.isArray(j)) { ids.push(...j); continue; }\n if (Array.isArray(j.data)) { ids.push(...j.data); continue; }\n if (j.id !== undefined) { ids.push(j.id); continue; }\n const vals = Object.values(j);\n if (vals.length === 1 && (typeof vals[0] === 'number' || typeof vals[0] === 'string')) ids.push(vals[0]);\n }\n return ids.filter(v => v !== null && v !== undefined && v !== '');\n}\n\nconst ids = collectIds($input.all());\nconst wanted = Number($('Build Employee Search Query').first().json.candidates_per_job || 5);\n\nif (ids.length === 0) {\n throw new Error('Employee search returned no matches. Loosen the filters (drop the country filter or the experience minimum).');\n}\n\nreturn ids.slice(0, wanted).map((id, i) => ({ json: { employee_id: id, rank: i + 1 } }));"
},
"typeVersion": 2
},
{
"id": "bf84c1ce-6b78-4a94-a013-8809d2f72b5d",
"name": "Fetch Employee Details",
"type": "n8n-nodes-coresignal-api.coresignal",
"position": [
8352,
2272
],
"parameters": {
"employeeId": "={{ $json.employee_id }}"
},
"credentials": {
"coresignalApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "473180c3-2c2b-4b2f-b1d1-1b0dedbde3cb",
"name": "Format Employees Shortlist",
"type": "n8n-nodes-base.code",
"position": [
8560,
2272
],
"parameters": {
"jsCode": "// Build one Slack mrkdwn message: profile link, short description, skills,\n// current title and the last 3 roles for each candidate.\nconst meta = $('Build Employee Search Query').first().json;\n\nfunction truncate(s, n) {\n s = String(s || '').replace(/\\s+/g, ' ').trim();\n return s.length > n ? s.slice(0, n - 1).trimEnd() + '\u2026' : s;\n}\n\nfunction fmtDate(exp, which) {\n const y = exp[which + '_year'];\n const m = exp[which + '_month'];\n if (!y) return which === 'date_to' ? 'Present' : '?';\n return m ? String(m).padStart(2, '0') + '/' + y : String(y);\n}\n\nfunction lastThree(experience) {\n const list = Array.isArray(experience) ? experience.slice() : [];\n list.sort((a, b) => {\n const av = (a.date_from_year || 0) * 100 + (a.date_from_month || 0);\n const bv = (b.date_from_year || 0) * 100 + (b.date_from_month || 0);\n if (bv !== av) return bv - av;\n return (a.order_in_profile || 99) - (b.order_in_profile || 99);\n });\n return list.slice(0, 3);\n}\n\nconst wanted = [\n ...(meta.filters?.must_have_skills || []),\n ...(meta.filters?.nice_to_have_skills || [])\n].map(s => String(s).toLowerCase());\n\nconst blocks = [];\nlet rank = 0;\n\nfor (const item of $input.all()) {\n const p = item.json || {};\n rank += 1;\n\n const name = p.full_name || 'Unknown';\n const url = p.linkedin_url\n ? (p.linkedin_url.startsWith('http') ? p.linkedin_url : 'https://' + p.linkedin_url)\n : null;\n const title = p.active_experience_title || String(p.headline || '').split(' at ')[0] || '\u2014';\n const location = p.location_full || p.location_country || '';\n const about = truncate(p.summary || p.headline || '', 260);\n\n // inferred_skills is a long, alphabetical, noisy list. Surface the ones that\n // actually match the job's requirements first, then pad with the rest.\n const all = (p.inferred_skills || []).map(s => String(s).toLowerCase());\n const matched = all.filter(s => wanted.some(w => s === w || s.includes(w) || w.includes(s)));\n const rest = all.filter(s => !matched.includes(s) && s.length > 3);\n const shown = [...new Set([...matched, ...rest])].slice(0, 12);\n const skills = shown.length\n ? shown.map(s => matched.includes(s) ? '*' + s + '*' : s).join(', ')\n : '\u2014';\n\n const expLines = lastThree(p.experience).map(e =>\n ' \u2022 *' + (e.position_title || '\u2014') + '* \u2014 ' + (e.company_name || '\u2014') +\n ' (' + fmtDate(e, 'date_from') + ' \u2192 ' + (e.active_experience ? 'Present' : fmtDate(e, 'date_to')) + ')'\n );\n\n const lines = [];\n lines.push('*' + rank + '. ' + (url ? '<' + url + '|' + name + '>' : name) + '*');\n lines.push(' ' + title + (location ? ' \u00b7 ' + location : ''));\n if (about) lines.push(' _' + about + '_');\n lines.push(' *Skills:* ' + skills);\n if (expLines.length) {\n lines.push(' *Experience:*');\n lines.push(expLines.join('\\n'));\n }\n blocks.push(lines.join('\\n'));\n}\n\nconst header = [\n '<@' + meta.slack_user_id + '> *' + (meta.filters?.search_summary || 'Candidate shortlist') + '*',\n ':briefcase: <' + meta.job_url + '|' + (meta.job_title || 'Job ad') + '> \u00b7 ' + (meta.job_company || '') + ' \u00b7 ' + (meta.job_location || ''),\n ':mag: Titles: ' + (meta.filters?.job_titles || []).join(', '),\n ':wrench: Must-have skills: ' + (meta.filters?.must_have_skills || []).join(', '),\n ':round_pushpin: ' + ((meta.filters?.countries || []).join(', ') || 'Any location') + ' \u00b7 ' + (meta.filters?.min_years_experience || 0) + '+ yrs \u00b7 ' + (meta.filters?.seniority || '')\n].join('\\n');\n\nreturn [{\n json: {\n slack_channel_id: meta.slack_channel_id,\n candidate_count: blocks.length,\n message: header + '\\n\\n' + blocks.join('\\n\\n')\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0981550c-101b-4d8f-aecc-40688d8277e7",
"name": "Post Employee Shortlist to Slack",
"type": "n8n-nodes-base.slack",
"position": [
8768,
2272
],
"parameters": {
"text": "={{ $json.message }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.slack_channel_id }}"
},
"otherOptions": {
"mrkdwn": true,
"unfurl_links": false,
"unfurl_media": false,
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "c5b25c1f-d5ef-42dd-b76e-0c6ba60c22b3",
"nodeGroups": [],
"connections": {
"Extract Job ID": {
"main": [
[
{
"node": "Retrieve Job Data from Coresignal",
"type": "main",
"index": 0
}
]
]
},
"Deduplicate Job Ads": {
"main": [
[
{
"node": "Loop Over Job Listings",
"type": "main",
"index": 0
}
]
]
},
"Fetch Employee Details": {
"main": [
[
{
"node": "Format Employees Shortlist",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Job Listings": {
"main": [
[],
[
{
"node": "Fetch Job Details from Coresignal",
"type": "main",
"index": 0
},
{
"node": "Post Acknowledgement on Slack",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Output Filters": {
"ai_outputParser": [
[
{
"node": "AI Extract Search Criteria",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Select Top Employee IDs": {
"main": [
[
{
"node": "Fetch Employee Details",
"type": "main",
"index": 0
}
]
]
},
"Set Job Candidates Limit": {
"main": [
[
{
"node": "Extract Job URL from Message",
"type": "main",
"index": 0
}
]
]
},
"AI Extract Search Criteria": {
"main": [
[
{
"node": "Build Employee Search Query",
"type": "main",
"index": 0
}
]
]
},
"Format Employees Shortlist": {
"main": [
[
{
"node": "Post Employee Shortlist to Slack",
"type": "main",
"index": 0
}
]
]
},
"Build Employee Search Query": {
"main": [
[
{
"node": "Search Employees in Coresignal",
"type": "main",
"index": 0
}
]
]
},
"When Job Ad Posted on Slack": {
"main": [
[
{
"node": "Set Job Candidates Limit",
"type": "main",
"index": 0
}
]
]
},
"Extract Job URL from Message": {
"main": [
[
{
"node": "Deduplicate Job Ads",
"type": "main",
"index": 0
}
]
]
},
"Search Employees in Coresignal": {
"main": [
[
{
"node": "Select Top Employee IDs",
"type": "main",
"index": 0
}
]
]
},
"Post Employee Shortlist to Slack": {
"main": [
[
{
"node": "Loop Over Job Listings",
"type": "main",
"index": 0
}
]
]
},
"Fetch Job Details from Coresignal": {
"main": [
[
{
"node": "Extract Job ID",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Job Data from Coresignal": {
"main": [
[
{
"node": "AI Extract Search Criteria",
"type": "main",
"index": 0
}
]
]
},
"Claude Model for Language Processing": {
"ai_languageModel": [
[
{
"node": "AI Extract Search Criteria",
"type": "ai_languageModel",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApicoresignalApislackApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow listens for new messages in a Slack channel, extracts job URLs, pulls the full job ad from Coresignal, uses Anthropic Claude to generate structured search filters, queries Coresignal’s employee database for matching profiles, and posts a ranked shortlist back to…
Source: https://n8n.io/workflows/18125/ — 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.
Agent - Twitter Reply Guy. Uses slackTrigger, chainLlm, slack, outputParserStructured. Event-driven trigger; 26 nodes.
This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest — You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract — OCR + parsing pulls out key fie
Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 91 nodes.
Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 87 nodes.
Content - Write Best Tools In Category Article. Uses formTrigger, httpRequest, slack, chainLlm. Event-driven trigger; 41 nodes.