This workflow corresponds to n8n.io template #16229 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request 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": "TiSJltTgLzEOzr17",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Verify resume claims against GitHub and LinkedIn and score candidate authenticity",
"tags": [],
"nodes": [
{
"id": "40144a16-3dcd-459c-88ff-08c93d4030de",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
816,
-336
],
"parameters": {
"width": 1000,
"height": 960,
"content": "## Verify resume claims against GitHub and LinkedIn and score candidate authenticity\n\nThis workflow automatically validates candidate resume claims by cross-referencing public data sources, portfolios, and skill benchmarks \u2014 then generates a structured authenticity score report.\n\n### Who's it for\n\u2022 Recruiters screening high-volume applicants\n\u2022 HR teams conducting pre-interview verification\n\u2022 Hiring managers validating technical skill claims\n\u2022 Staffing agencies performing candidate due diligence\n\n### How it works / What it does\n1. Accepts resume data via webhook or scheduled batch\n2. Parses and extracts all verifiable claims\n3. Cross-references GitHub, LinkedIn, education & employment sources\n4. Benchmarks technical skills against known standards\n5. Calculates weighted authenticity scores per claim category\n6. Generates discrepancy report and confidence levels\n7. Logs full verification report to Google Sheets\n\n### How to set up\n1. Import this workflow\n2. Set up credentials (GitHub API, LinkedIn API, OpenAI/Anthropic, Google Sheets)\n3. Configure scoring weights and threshold values in the Set node\n4. Activate workflow\n\n### Requirements\n\u2022 Webhook endpoint or scheduled trigger\n\u2022 GitHub API token\n\u2022 LinkedIn API access (or RapidAPI LinkedIn proxy)\n\u2022 Google Sheets (for output logging)\n\u2022 OpenAI / Anthropic API\n\n### How to customize the workflow\n\u2022 Adjust scoring weights in the Set Scoring Config node\n\u2022 Add/remove verification sources in the HTTP Request nodes\n\u2022 Modify Python claim extractor keywords\n\u2022 Update Google Sheet columns to match your ATS schema"
},
"typeVersion": 1
},
{
"id": "163c77a8-847b-4ebf-8c81-60d00b9a44be",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1856,
-96
],
"parameters": {
"color": 4,
"width": 780,
"height": 520,
"content": "## 1. Trigger & Resume Intake"
},
"typeVersion": 1
},
{
"id": "80d0f75a-2f3c-454f-974d-f13e96a1ff3e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2688,
-176
],
"parameters": {
"color": 4,
"width": 820,
"height": 580,
"content": "## 2. Claim Extraction & Parsing"
},
"typeVersion": 1
},
{
"id": "a94d1a69-c66c-4684-b50f-8a4061cb1bfd",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
3536,
-224
],
"parameters": {
"color": 4,
"width": 1140,
"height": 880,
"content": "## 3. Cross-Reference Verification"
},
"typeVersion": 1
},
{
"id": "79f320c4-bc1f-43ee-abf7-f4f8d7d3bd75",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4848,
-176
],
"parameters": {
"color": 4,
"width": 772,
"height": 728,
"content": "## 4. Scoring & Report Generation"
},
"typeVersion": 1
},
{
"id": "45e86f4d-5b35-4645-bd94-65709e85e37f",
"name": "Webhook - Resume Submission",
"type": "n8n-nodes-base.webhook",
"position": [
2016,
32
],
"parameters": {
"path": "resume-verify-inbound",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "2f6ce57e-0e7b-4aab-b59e-40ea7e20ecb9",
"name": "Schedule - Daily Batch Verify",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2016,
224
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * 1-5"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "2ab30679-fe28-403b-bb49-67d46a8b83d7",
"name": "Prepare Candidate Context",
"type": "n8n-nodes-base.set",
"position": [
2256,
128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "candidateId",
"type": "string",
"value": "={{ $json.candidateId || $json.body?.candidateId || ('CAND-' + Date.now()) }}"
},
{
"name": "candidateName",
"type": "string",
"value": "={{ $json.name || $json.body?.name || 'Unknown' }}"
},
{
"name": "resumeText",
"type": "string",
"value": "={{ $json.resumeText || $json.body?.resumeText || '' }}"
},
{
"name": "linkedinUrl",
"type": "string",
"value": "={{ $json.linkedinUrl || $json.body?.linkedinUrl || '' }}"
},
{
"name": "githubUsername",
"type": "string",
"value": "={{ $json.githubUsername || $json.body?.githubUsername || '' }}"
},
{
"name": "portfolioUrl",
"type": "string",
"value": "={{ $json.portfolioUrl || $json.body?.portfolioUrl || '' }}"
},
{
"name": "submittedAt",
"type": "string",
"value": "={{ new Date().toISOString() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1a904ae1-351c-4642-8774-3b277ee70454",
"name": "Set Scoring Config",
"type": "n8n-nodes-base.set",
"position": [
2480,
128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "scoreWeights",
"type": "object",
"value": "={{ { employment: 0.35, education: 0.25, skills: 0.25, github: 0.10, certifications: 0.05 } }}"
},
{
"name": "confidenceThresholds",
"type": "object",
"value": "={{ { high: 0.80, medium: 0.55, low: 0.30 } }}"
},
{
"name": "flagThreshold",
"type": "number",
"value": "={{ 0.40 }}"
},
{
"name": "jobKeywords",
"type": "array",
"value": "={{ ['engineer', 'developer', 'manager', 'analyst', 'architect', 'lead', 'senior', 'specialist', 'consultant', 'director'] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "baf95db8-2749-415c-8883-b4ccbbbd1ec6",
"name": "Python - Extract Resume Claims",
"type": "n8n-nodes-base.code",
"position": [
2832,
128
],
"parameters": {
"mode": "runOnceForEachItem",
"language": "pythonNative",
"pythonCode": "# ============================================================\n# EXTRACT & PARSE VERIFIABLE CLAIMS FROM RESUME TEXT\n# ============================================================\n\nitem = _input.item.json\n\nresume = (item.get('resumeText', '') or '').strip()\n\nimport re\n\n# ============================================================\n# 1. EMPLOYMENT HISTORY EXTRACTION\n# ============================================================\n\nemployment_patterns = [\n r'(?P<title>[A-Z][\\w\\s]+?(?:Engineer|Developer|Manager|Analyst|Architect|Lead|Director|Specialist|Consultant|Designer|Scientist)[\\w\\s]*?)\\s*(?:at|@|,|-)\\s*(?P<company>[A-Z][\\w\\s&.,]+?)\\s*(?:from|\\()?\\s*(?P<start>(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?|\\d{4})[\\w\\s,]*)\\s*(?:\u2013|-|to|\u2014)\\s*(?P<end>(?:Present|Current|Now|Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?|\\d{4})[\\w\\s,]*)',\n]\n\nemployment_claims = []\nseen_employment = set()\n\nfor pat in employment_patterns:\n for m in re.finditer(pat, resume, re.IGNORECASE):\n title = (m.group('title') or '').strip()\n company = (m.group('company') or '').strip()\n start = (m.group('start') or '').strip()\n end = (m.group('end') or '').strip()\n\n dedup_key = f\"{title.lower()}|{company.lower()}\"\n if dedup_key in seen_employment:\n continue\n seen_employment.add(dedup_key)\n\n employment_claims.append({\n 'type': 'employment',\n 'title': title,\n 'company': company,\n 'start': start,\n 'end': end\n })\n\n# ============================================================\n# 2. EDUCATION EXTRACTION\n# ============================================================\n\nedu_pattern = r'(?P<degree>Bachelor(?:\\'?s)?|Master(?:\\'?s)?|PhD|Ph\\.D|B\\.?S\\.?|M\\.?S\\.?|MBA|B\\.?E\\.?|M\\.?E\\.?|Associate(?:\\'?s)?|Doctorate)[\\w\\s.]*?(?:in|of)?\\s*(?P<field>[\\w\\s]+?)\\s*(?:from|at|,|-)\\s*(?P<institution>[A-Z][\\w\\s]+?(?:University|College|Institute|School|Academy)[\\w\\s]*?)(?=\\n|,|\\.|$)'\n\nedu_claims = []\nseen_edu = set()\n\nfor m in re.finditer(edu_pattern, resume, re.IGNORECASE):\n degree = (m.group('degree') or '').strip()\n field = (m.group('field') or '').strip()\n institution = (m.group('institution') or '').strip()\n\n dedup_key = f\"{degree.lower()}|{institution.lower()}\"\n if dedup_key in seen_edu:\n continue\n seen_edu.add(dedup_key)\n\n edu_claims.append({\n 'type': 'education',\n 'degree': degree,\n 'field': field,\n 'institution': institution\n })\n\n# ============================================================\n# 3. SKILLS EXTRACTION\n# ============================================================\n\nskill_section_pattern = r'(?:Technical\\s+)?(?:Skills?|Technologies?|Tech\\s+Stack|Tools?|Expertise|Proficiencies?|Competencies?)[:\\s\\n]+([^\\n#]{10,300})'\n\nskill_list = []\nseen_skills = set()\n\nfor section_match in re.finditer(skill_section_pattern, resume, re.IGNORECASE):\n raw_section = section_match.group(1) or ''\n raw_skills = re.split(r'[,\\n|\u2022\u00b7/]', raw_section)\n\n for s in raw_skills:\n cleaned = s.strip().strip('*-\u2013').strip()\n if 2 < len(cleaned) < 40 and cleaned.lower() not in seen_skills:\n seen_skills.add(cleaned.lower())\n skill_list.append(cleaned)\n\n if len(skill_list) >= 30:\n break\n\nskill_claims = [{'type': 'skill', 'name': s} for s in skill_list]\n\n# ============================================================\n# 4. CERTIFICATION EXTRACTION\n# ============================================================\n\ncert_patterns = [\n r'(?:Certified|Certificate|Certification\\s+in|Certified\\s+in)[:\\s]+([A-Z][\\w\\s]+?(?:AWS|GCP|Azure|PMP|CPA|CFA|CISSP|CompTIA|Scrum|Google|Microsoft|Oracle|Salesforce|PMI|ITIL|CEH|CISM|CISA)[\\w\\s]*?)(?=\\n|,|\\.|$)',\n r'(?:AWS|GCP|Azure|Google|Microsoft|Oracle|Salesforce)\\s+(?:Certified\\s+)?[\\w\\s]+?(?:Associate|Professional|Expert|Developer|Architect|Engineer|Practitioner)(?=\\n|,|\\.|$)',\n]\n\ncert_claims = []\nseen_certs = set()\n\nfor pat in cert_patterns:\n for m in re.finditer(pat, resume, re.IGNORECASE):\n cert_name = (m.group(1) if m.lastindex and m.lastindex >= 1 else m.group(0)).strip()\n if cert_name.lower() not in seen_certs:\n seen_certs.add(cert_name.lower())\n cert_claims.append({'type': 'certification', 'name': cert_name})\n\n# ============================================================\n# 5. YEAR RANGE SANITY CHECK\n# ============================================================\n\nyear_pattern = r'\\b(19[8-9]\\d|20[0-2]\\d)\\b'\nyears_found = [int(y) for y in re.findall(year_pattern, resume)]\ntimeline_flag = False\ntimeline_note = ''\n\nif len(years_found) >= 2:\n span = max(years_found) - min(years_found)\n if span > 40:\n timeline_flag = True\n timeline_note = f'Suspicious year span detected: {min(years_found)} to {max(years_found)}'\n\n# ============================================================\n# 6. ASSEMBLE OUTPUT\n# ============================================================\n\nall_claims = employment_claims + edu_claims + skill_claims + cert_claims\n\nreturn {\n 'json': {\n **item,\n 'parsedClaims': all_claims,\n 'claimCounts': {\n 'employment': len(employment_claims),\n 'education': len(edu_claims),\n 'skills': len(skill_claims),\n 'certifications': len(cert_claims),\n 'total': len(all_claims)\n },\n 'timelineFlag': timeline_flag,\n 'timelineNote': timeline_note,\n 'resumeLength': len(resume)\n }\n}"
},
"typeVersion": 2
},
{
"id": "36564e60-7005-4b4d-8346-5d3db3b28b9a",
"name": "Filter - Has Valid Claims",
"type": "n8n-nodes-base.filter",
"position": [
3088,
128
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.claimCounts?.total }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8d3f86c1-d478-4050-8b02-4472f577056d",
"name": "GitHub - Fetch Profile",
"type": "n8n-nodes-base.httpRequest",
"position": [
3616,
0
],
"parameters": {
"url": "=https://api.github.com/users/{{ $json.githubUsername }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_GITHUB_TOKEN"
},
{
"name": "Accept",
"value": "application/vnd.github.v3+json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "60981c35-680f-43ff-829a-66e16e25ac25",
"name": "GitHub - Fetch Repositories",
"type": "n8n-nodes-base.httpRequest",
"position": [
3616,
176
],
"parameters": {
"url": "=https://api.github.com/users/{{ $json.githubUsername }}/repos?per_page=30&sort=updated",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_GITHUB_TOKEN"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "09676d79-e7d0-462f-b30a-32892c671efb",
"name": "LinkedIn - Fetch Public Profile",
"type": "n8n-nodes-base.httpRequest",
"position": [
3616,
352
],
"parameters": {
"url": "=https://api.rapidapi.com/linkedin/profile?url={{ $json.linkedinUrl }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-RapidAPI-Key",
"value": "YOUR_RAPIDAPI_KEY"
},
{
"name": "X-RapidAPI-Host",
"value": "linkedin-profiles-api.p.rapidapi.com"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "329372c4-2a23-454e-8f5d-6838dfbfe17e",
"name": "JS - Aggregate External Evidence",
"type": "n8n-nodes-base.code",
"position": [
3936,
176
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\n\n// Aggregate all external source data\nconst githubProfile = item.githubProfile || {};\nconst githubRepos = item.githubRepos || [];\nconst linkedinData = item.linkedinData || {};\n\n// GitHub-derived evidence\nconst githubEvidence = {\n profileExists: !!githubProfile.login,\n publicRepos: githubProfile.public_repos || 0,\n followers: githubProfile.followers || 0,\n accountAgeDays: githubProfile.created_at\n ? Math.floor((Date.now() - new Date(githubProfile.created_at)) / 86400000)\n : 0,\n topLanguages: [...new Set(\n (githubRepos || [])\n .map(r => r.language)\n .filter(Boolean)\n )].slice(0, 10),\n repoNames: (githubRepos || []).map(r => r.name),\n stargazersTotal: (githubRepos || []).reduce((s, r) => s + (r.stargazers_count || 0), 0)\n};\n\n// LinkedIn-derived evidence\nconst linkedinEvidence = {\n profileExists: !!linkedinData.firstName,\n displayName: `${linkedinData.firstName || ''} ${linkedinData.lastName || ''}`.trim(),\n headline: linkedinData.headline || '',\n positions: linkedinData.positions?.values || [],\n educations: linkedinData.educations?.values || [],\n skills: (linkedinData.skills?.values || []).map(s => s.skill?.name).filter(Boolean)\n};\n\nreturn {\n json: {\n ...item,\n githubEvidence,\n linkedinEvidence\n }\n};"
},
"typeVersion": 2
},
{
"id": "e1cc8527-c205-4c9c-a4ab-ec263d2c1c08",
"name": "AI - Cross-Reference & Verify Claims",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
4160,
176
],
"parameters": {
"text": "=You are an expert resume verification AI. Analyze the candidate's resume claims against external evidence and produce a structured JSON verification report.\n\n## Candidate Information\nName: {{ $json.candidateName }}\nCandidate ID: {{ $json.candidateId }}\n\n## Resume Claims (parsed)\n{{ JSON.stringify($json.parsedClaims, null, 2) }}\n\n## GitHub Evidence\n{{ JSON.stringify($json.githubEvidence, null, 2) }}\n\n## LinkedIn Evidence\n{{ JSON.stringify($json.linkedinEvidence, null, 2) }}\n\n## Your Task\nFor each parsed claim, determine:\n1. Verification status: VERIFIED / UNVERIFIED / FLAGGED / INCONCLUSIVE\n2. Confidence score: 0.0 to 1.0\n3. Evidence found or discrepancy noted\n4. Specific flag reason (if FLAGGED)\n\nAlso assess:\n- Do the skills claimed match the languages/repos found on GitHub?\n- Does employment history align with LinkedIn positions?\n- Are the claimed years of experience consistent across sources?\n- Are there any date overlaps or impossible timelines?\n\nRespond ONLY with valid JSON in this exact structure:\n{\n \"verifiedClaims\": [\n {\n \"claimType\": \"skill|employment|education|certification\",\n \"claimValue\": \"string describing the claim\",\n \"status\": \"VERIFIED|UNVERIFIED|FLAGGED|INCONCLUSIVE\",\n \"confidenceScore\": 0.0,\n \"evidenceSummary\": \"brief description\",\n \"flagReason\": \"null or reason string\"\n }\n ],\n \"discrepancies\": [\n {\n \"type\": \"date_overlap|skill_mismatch|employer_unverifiable|credential_gap\",\n \"description\": \"string\",\n \"severity\": \"HIGH|MEDIUM|LOW\"\n }\n ],\n \"overallNotes\": \"string\"\n}",
"options": {},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "5930c09c-c1fb-44f0-8d21-7b94258501c7",
"name": "JS - Calculate Authenticity Score",
"type": "n8n-nodes-base.code",
"position": [
4496,
176
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\n\n// Parse AI response\nlet aiResult = {};\ntry {\n const raw = item.output || item.response || item.text || '{}';\n const clean = raw.replace(/```json|```/g, '').trim();\n aiResult = JSON.parse(clean);\n} catch(e) {\n aiResult = { verifiedClaims: [], discrepancies: [], overallNotes: 'Parse error: ' + e.message };\n}\n\nconst claims = aiResult.verifiedClaims || [];\nconst discrepancies = aiResult.discrepancies || [];\nconst weights = item.scoreWeights || { employment: 0.35, education: 0.25, skills: 0.25, github: 0.10, certifications: 0.05 };\nconst flagThreshold = item.flagThreshold || 0.40;\n\n// Group claims by type\nconst grouped = {};\nclaims.forEach(c => {\n const t = c.claimType || 'other';\n if (!grouped[t]) grouped[t] = [];\n grouped[t].push(c);\n});\n\n// Calculate category scores\nconst categoryScores = {};\nObject.entries(grouped).forEach(([type, claimsArr]) => {\n const avg = claimsArr.reduce((s, c) => s + (c.confidenceScore || 0), 0) / claimsArr.length;\n const verified = claimsArr.filter(c => c.status === 'VERIFIED').length;\n const flagged = claimsArr.filter(c => c.status === 'FLAGGED').length;\n categoryScores[type] = {\n averageConfidence: +avg.toFixed(3),\n verifiedCount: verified,\n flaggedCount: flagged,\n totalCount: claimsArr.length,\n verificationRate: +(verified / claimsArr.length).toFixed(3)\n };\n});\n\n// GitHub bonus scoring\nconst gh = item.githubEvidence || {};\nconst githubScore = gh.profileExists\n ? Math.min(1.0, 0.4 + (gh.publicRepos > 10 ? 0.3 : gh.publicRepos * 0.03) + (gh.accountAgeDays > 365 ? 0.3 : gh.accountAgeDays / 365 * 0.3))\n : 0.0;\n\n// Weighted overall authenticity score\nconst skillScore = categoryScores['skill']?.averageConfidence || 0;\nconst employmentScore = categoryScores['employment']?.averageConfidence || 0;\nconst educationScore = categoryScores['education']?.averageConfidence || 0;\nconst certScore = categoryScores['certification']?.averageConfidence || 0;\n\nconst overallScore = +(\n (employmentScore * weights.employment) +\n (educationScore * weights.education) +\n (skillScore * weights.skills) +\n (githubScore * weights.github) +\n (certScore * weights.certifications)\n).toFixed(3);\n\n// Determine overall verdict\nlet verdict = 'LOW_CONFIDENCE';\nif (overallScore >= 0.80) verdict = 'HIGHLY_AUTHENTIC';\nelse if (overallScore >= 0.60) verdict = 'LIKELY_AUTHENTIC';\nelse if (overallScore >= 0.40) verdict = 'PARTIALLY_VERIFIED';\nelse if (overallScore >= 0.20) verdict = 'SUSPICIOUS';\n\nconst highSeverityFlags = discrepancies.filter(d => d.severity === 'HIGH').length;\nif (highSeverityFlags >= 2) verdict = 'FLAGGED_FOR_REVIEW';\n\n// Final report\nconst report = {\n candidateId: item.candidateId,\n candidateName: item.candidateName,\n verifiedAt: new Date().toISOString(),\n overallAuthenticityScore: overallScore,\n verdict,\n categoryScores,\n githubScore: +githubScore.toFixed(3),\n discrepancies,\n flaggedClaimsCount: claims.filter(c => c.status === 'FLAGGED').length,\n verifiedClaimsCount: claims.filter(c => c.status === 'VERIFIED').length,\n totalClaimsAnalyzed: claims.length,\n verifiedClaims: claims,\n overallNotes: aiResult.overallNotes || '',\n linkedinUrl: item.linkedinUrl,\n githubUsername: item.githubUsername,\n status: 'Completed'\n};\n\nreturn { json: { ...item, report } };"
},
"typeVersion": 2
},
{
"id": "17b3b6fa-3f90-4cde-b382-f68a17130e5a",
"name": "Update Google Sheet - Verification Log",
"type": "n8n-nodes-base.httpRequest",
"position": [
5024,
64
],
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/ResumeVerifications!A1:append?valueInputOption=USER_ENTERED",
"method": "POST",
"options": {},
"jsonBody": "={\n \"values\": [[\n \"{{ $json.report.verifiedAt }}\",\n \"{{ $json.report.candidateId }}\",\n \"{{ $json.report.candidateName }}\",\n \"{{ $json.report.overallAuthenticityScore }}\",\n \"{{ $json.report.verdict }}\",\n \"{{ $json.report.verifiedClaimsCount }}\",\n \"{{ $json.report.flaggedClaimsCount }}\",\n \"{{ $json.report.totalClaimsAnalyzed }}\",\n \"{{ $json.report.discrepancies.length }}\",\n \"{{ $json.report.githubScore }}\",\n \"{{ $json.githubUsername }}\",\n \"{{ $json.linkedinUrl }}\",\n \"{{ $json.report.overallNotes }}\"\n ]]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_GOOGLE_OAUTH_TOKEN"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "8c4f522b-8b6f-4666-8e1f-80607d313889",
"name": "Send Verification Report Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
5024,
288
],
"parameters": {
"url": "https://api.sendgrid.com/v3/mail/send",
"method": "POST",
"options": {},
"jsonBody": "={\n \"personalizations\": [{\"to\": [{\"email\": \"recruiter@yourcompany.com\"}]}],\n \"from\": {\"email\": \"verify-bot@yourcompany.com\", \"name\": \"Resume Verifier\"},\n \"subject\": \"Resume Verification: {{ $json.report.candidateName }} \u2014 Score: {{ $json.report.overallAuthenticityScore }} ({{ $json.report.verdict }})\",\n \"content\": [{\n \"type\": \"text/plain\",\n \"value\": \"Verification complete for {{ $json.report.candidateName }}\\nCandidate ID: {{ $json.report.candidateId }}\\nAuthenticity Score: {{ $json.report.overallAuthenticityScore }}/1.0\\nVerdict: {{ $json.report.verdict }}\\nClaims Verified: {{ $json.report.verifiedClaimsCount }} of {{ $json.report.totalClaimsAnalyzed }}\\nFlags Raised: {{ $json.report.flaggedClaimsCount }}\\nDiscrepancies: {{ $json.report.discrepancies.length }}\\nNotes: {{ $json.report.overallNotes }}\\n\\nFull report logged to Google Sheets.\"\n }]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_SENDGRID_API_KEY"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "1c0a37bd-9d86-440a-97e0-8281828f8d70",
"name": "Webhook Response - Return Report",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
5280,
176
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json.report) }}"
},
"typeVersion": 1.1
},
{
"id": "21b74c49-3db5-4243-b647-667b18700524",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
4128,
384
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "9e1b90f1-f41f-45db-8b4b-9441f25cdf76",
"name": "Wait - Rate Limit Buffer",
"type": "n8n-nodes-base.wait",
"position": [
3296,
128
],
"parameters": {},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "736c1d7b-01a3-4374-b367-a0b35cf84487",
"connections": {
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI - Cross-Reference & Verify Claims",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Set Scoring Config": {
"main": [
[
{
"node": "Python - Extract Resume Claims",
"type": "main",
"index": 0
}
]
]
},
"GitHub - Fetch Profile": {
"main": [
[
{
"node": "JS - Aggregate External Evidence",
"type": "main",
"index": 0
}
]
]
},
"Wait - Rate Limit Buffer": {
"main": [
[
{
"node": "GitHub - Fetch Profile",
"type": "main",
"index": 0
},
{
"node": "GitHub - Fetch Repositories",
"type": "main",
"index": 0
},
{
"node": "LinkedIn - Fetch Public Profile",
"type": "main",
"index": 0
}
]
]
},
"Filter - Has Valid Claims": {
"main": [
[
{
"node": "Wait - Rate Limit Buffer",
"type": "main",
"index": 0
}
]
]
},
"Prepare Candidate Context": {
"main": [
[
{
"node": "Set Scoring Config",
"type": "main",
"index": 0
}
]
]
},
"GitHub - Fetch Repositories": {
"main": [
[
{
"node": "JS - Aggregate External Evidence",
"type": "main",
"index": 0
}
]
]
},
"Webhook - Resume Submission": {
"main": [
[
{
"node": "Prepare Candidate Context",
"type": "main",
"index": 0
}
]
]
},
"Schedule - Daily Batch Verify": {
"main": [
[
{
"node": "Prepare Candidate Context",
"type": "main",
"index": 0
}
]
]
},
"Python - Extract Resume Claims": {
"main": [
[
{
"node": "Filter - Has Valid Claims",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn - Fetch Public Profile": {
"main": [
[
{
"node": "JS - Aggregate External Evidence",
"type": "main",
"index": 0
}
]
]
},
"JS - Aggregate External Evidence": {
"main": [
[
{
"node": "AI - Cross-Reference & Verify Claims",
"type": "main",
"index": 0
}
]
]
},
"JS - Calculate Authenticity Score": {
"main": [
[
{
"node": "Update Google Sheet - Verification Log",
"type": "main",
"index": 0
},
{
"node": "Send Verification Report Email",
"type": "main",
"index": 0
},
{
"node": "Webhook Response - Return Report",
"type": "main",
"index": 0
}
]
]
},
"AI - Cross-Reference & Verify Claims": {
"main": [
[
{
"node": "JS - Calculate Authenticity Score",
"type": "main",
"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.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts resume submissions via webhook or a weekday schedule, extracts verifiable claims from the resume text, cross-checks them against GitHub and LinkedIn data, uses OpenAI to generate a verification report, calculates an authenticity score, logs results to…
Source: https://n8n.io/workflows/16229/ — 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.
VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
AI Customer Service Automation - Portfolio. Uses googleSheets, httpRequest, agent, memoryBufferWindow. Webhook trigger; 91 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.