This workflow follows the Form Trigger → 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 →
{
"name": "Competitor Poach - Auto Daily Limit",
"nodes": [
{
"parameters": {
"formTitle": "Competitor Post Scraper",
"formDescription": "Scrape competitor post and auto-send connections (max 8/day, auto-continues next day)",
"formFields": {
"values": [
{
"fieldLabel": "LinkedIn Post URL",
"placeholder": "https://www.linkedin.com/feed/update/...",
"requiredField": true
},
{
"fieldLabel": "Competitor Name",
"placeholder": "e.g., Salesforce",
"requiredField": true
},
{
"fieldLabel": "Send Message?",
"fieldType": "dropdown",
"fieldOptions": {
"values": [
{
"option": "Yes"
},
{
"option": "No"
}
]
},
"requiredField": true
},
{
"fieldLabel": "Message Template",
"fieldType": "textarea",
"placeholder": "Hi {firstName}, saw your comment. Would love to connect!"
}
]
},
"options": {}
},
"id": "trigger",
"name": "Form Trigger",
"type": "n8n-nodes-base.formTrigger",
"position": [
0,
264
],
"typeVersion": 2.2
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "daily-limit",
"name": "dailyLimit",
"value": 10,
"type": "number"
},
{
"id": "post-url",
"name": "postUrl",
"value": "={{ $json['LinkedIn Post URL'] }}",
"type": "string"
},
{
"id": "competitor",
"name": "competitor",
"value": "={{ $json['Competitor Name'].toLowerCase() }}",
"type": "string"
},
{
"id": "send-msg",
"name": "sendMessage",
"value": "={{ $json['Send Message?'] === 'Yes' }}",
"type": "boolean"
},
{
"id": "template",
"name": "template",
"value": "={{ $json['Message Template'] || 'Hi {firstName}, saw your comment. Would love to connect!' }}",
"type": "string"
}
]
},
"options": {}
},
"id": "set-config",
"name": "Set Config",
"type": "n8n-nodes-base.set",
"position": [
224,
264
],
"typeVersion": 3.4
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/posts/comments/all",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ postUrl: $json.postUrl, maxComments: 500 }) }}",
"options": {}
},
"id": "get-comments",
"name": "Get Comments",
"type": "n8n-nodes-base.httpRequest",
"position": [
448,
264
],
"typeVersion": 4.2,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const config = $('Set Config').first().json;\nconst response = $input.first().json;\n\n// Handle array wrapper - API returns [{success, comments, ...}]\nconst comments = Array.isArray(response) ? response[0]?.comments : response.comments;\n\nif (!comments || comments.length === 0) {\n return [{ json: { error: 'No comments found', profiles: [] } }];\n}\n\n// Deduplicate by profileUrl\nconst seen = new Set();\nconst profiles = [];\n\nfor (const c of comments) {\n if (c.profileUrl && !seen.has(c.profileUrl)) {\n seen.add(c.profileUrl);\n profiles.push({\n profileId: c.publicIdentifier,\n name: c.authorName || '',\n profileUrl: c.profileUrl,\n competitor: config.competitor,\n sendMessage: config.sendMessage,\n template: config.template,\n dailyLimit: config.dailyLimit\n });\n }\n}\n\nreturn profiles.map(p => ({ json: p }));"
},
"id": "extract-profiles",
"name": "Extract Profiles",
"type": "n8n-nodes-base.code",
"position": [
672,
264
],
"typeVersion": 2
},
{
"parameters": {
"options": {}
},
"id": "loop",
"name": "Loop",
"type": "n8n-nodes-base.splitInBatches",
"position": [
896,
264
],
"typeVersion": 3
},
{
"parameters": {
"jsCode": "const item = $input.first().json;\nconst staticData = $getWorkflowStaticData('global');\nconst today = new Date().toISOString().split('T')[0];\nconst DAILY_LIMIT = item.dailyLimit || 8;\n\n// Reset if new day\nif (staticData.date !== today) {\n staticData.date = today;\n staticData.count = 0;\n}\n\nconst count = staticData.count || 0;\nconst limitReached = count >= DAILY_LIMIT;\n\nreturn [{ json: { ...item, limitReached, sentToday: count, dailyLimit: DAILY_LIMIT } }];"
},
"id": "check-limit",
"name": "Check Limit",
"type": "n8n-nodes-base.code",
"position": [
1120,
264
],
"typeVersion": 2
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.limitReached }}",
"rightValue": true
}
]
},
"options": {}
},
"id": "if-limit",
"name": "Limit Reached?",
"type": "n8n-nodes-base.if",
"position": [
1344,
264
],
"typeVersion": 2
},
{
"parameters": {
"jsCode": "// Calculate seconds until midnight\nconst now = new Date();\nconst tomorrow = new Date(now);\ntomorrow.setDate(tomorrow.getDate() + 1);\ntomorrow.setHours(0, 0, 5, 0); // 12:00:05 AM\n\nconst secondsUntilTomorrow = Math.ceil((tomorrow - now) / 1000);\n\n// Reset counter for new day\nconst staticData = $getWorkflowStaticData('global');\nconst newDate = tomorrow.toISOString().split('T')[0];\nstaticData.date = newDate;\nstaticData.count = 0;\n\nreturn [{ json: { ...$input.first().json, waitSeconds: secondsUntilTomorrow, resumeAt: tomorrow.toISOString(), limitReached: false, sentToday: 0 } }];"
},
"id": "wait-tomorrow",
"name": "Wait Until Tomorrow",
"type": "n8n-nodes-base.code",
"position": [
1568,
192
],
"typeVersion": 2
},
{
"parameters": {
"amount": "={{ $json.waitSeconds }}"
},
"id": "wait-node",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
1792,
192
],
"typeVersion": 1.1
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/profile",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ profileId: $json.profileId }) }}",
"options": {}
},
"id": "get-profile",
"name": "Get Profile",
"type": "n8n-nodes-base.httpRequest",
"position": [
2016,
264
],
"typeVersion": 4.2,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const profile = $input.first().json;\nconst item = $('Loop').first().json;\n\n// Skip if competitor employee\nconst company = (profile.currentCompany || profile.headline || '').toLowerCase();\nif (company.includes(item.competitor)) {\n return [{ json: { skip: true, reason: 'competitor_employee', profileId: item.profileId, name: item.name } }];\n}\n\n// Build message if enabled\nlet message = null;\nif (item.sendMessage && item.template) {\n const firstName = profile.firstName || item.name?.split(' ')[0] || 'there';\n message = item.template.replace('{firstName}', firstName);\n if (message.length > 300) message = message.slice(0, 297) + '...';\n}\n\nreturn [{ json: { skip: false, profileId: item.profileId, name: item.name, firstName: profile.firstName, message } }];"
},
"id": "prepare-send",
"name": "Prepare & Filter",
"type": "n8n-nodes-base.code",
"position": [
2240,
264
],
"typeVersion": 2
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "not-skip",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.skip }}",
"rightValue": false
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-skip",
"name": "Should Send?",
"type": "n8n-nodes-base.if",
"position": [
2464,
264
],
"typeVersion": 2
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/connect",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ (() => { const b = {profileId: $json.profileId}; if($json.message) b.customMessage = $json.message; return JSON.stringify(b); })() }}",
"options": {}
},
"id": "send-connection",
"name": "Send Connection",
"type": "n8n-nodes-base.httpRequest",
"position": [
2688,
192
],
"typeVersion": 4.2,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const res = $input.first().json;\nconst item = $('Loop').first().json;\nconst sent = res.success === true;\n\n// Increment daily counter if sent successfully\nif (sent) {\n const staticData = $getWorkflowStaticData('global');\n staticData.count = (staticData.count || 0) + 1;\n}\n\nreturn [{ json: { profileId: item.profileId, name: item.name, sent, error: res.error || null } }];"
},
"id": "log-increment",
"name": "Log & Increment",
"type": "n8n-nodes-base.code",
"position": [
2912,
192
],
"typeVersion": 2
},
{
"parameters": {
"amount": 3
},
"id": "wait-3s",
"name": "Wait 3s",
"type": "n8n-nodes-base.wait",
"position": [
3152,
400
],
"typeVersion": 1.1
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "results",
"options": {}
},
"id": "aggregate",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
1136,
-64
],
"typeVersion": 1
},
{
"parameters": {
"jsCode": "const results = $input.first().json.results || [];\nconst sent = results.filter(r => r.sent).length;\nconst skipped = results.filter(r => r.skip).length;\nconst staticData = $getWorkflowStaticData('global');\n\nreturn [{ json: {\n status: 'completed',\n total: results.length,\n sent,\n skipped,\n failed: results.length - sent - skipped,\n dailyCountNow: staticData.count || 0,\n results\n} }];"
},
"id": "summary",
"name": "Summary",
"type": "n8n-nodes-base.code",
"position": [
1344,
-64
],
"typeVersion": 2
},
{
"parameters": {
"content": "## Competitor Poach - Auto Daily Limit\n\nAutomatically scrape LinkedIn post commenters and send connection requests with built-in daily limits and auto-scheduling.\n\n### Who is this for?\n- Sales teams targeting competitor audiences\n- Recruiters finding engaged professionals\n- Marketers building targeted networks\n\n### How it works\n1. Submit a LinkedIn post URL via the form\n2. Extracts all commenters (up to 500)\n3. Loops through each profile one-by-one\n4. Checks daily limit before each send\n5. If limit reached \u2192 waits until midnight \u2192 resumes\n6. Filters out competitor employees\n7. Sends personalized connection requests\n8. Returns summary when complete\n\n### Setup steps\n1. Add ConnectSafely.ai API credentials (Header Auth)\n2. Configure `dailyLimit` in Set Config node (default: 8)\n3. Activate the workflow\n4. Access form at: `/webhook/competitor-poach`\n\n### Customization\n- Change `dailyLimit` in Set Config node\n- Modify message template in the form\n- Adjust `Wait 3s` for different rate limiting\n\n### Requirements\n- ConnectSafely.ai account with API access\n- n8n credentials: Header Auth type",
"height": 854,
"width": 594,
"color": 2
},
"id": "sticky-overview",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-688,
-160
]
},
{
"parameters": {
"content": "## 1. Setup & Data Collection",
"height": 262,
"width": 702,
"color": 5
},
"id": "sticky-section1",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-64,
176
]
},
{
"parameters": {
"content": "## 2. Processing Loop with Daily Limit",
"height": 300,
"width": 2350,
"color": 7
},
"id": "sticky-section2",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
777,
124
]
},
{
"parameters": {
"content": "## 3. Results & Response",
"height": 278,
"width": 650,
"color": 4
},
"id": "sticky-section3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1072,
-176
]
}
],
"connections": {
"Form Trigger": {
"main": [
[
{
"node": "Set Config",
"type": "main",
"index": 0
}
]
]
},
"Set Config": {
"main": [
[
{
"node": "Get Comments",
"type": "main",
"index": 0
}
]
]
},
"Get Comments": {
"main": [
[
{
"node": "Extract Profiles",
"type": "main",
"index": 0
}
]
]
},
"Extract Profiles": {
"main": [
[
{
"node": "Loop",
"type": "main",
"index": 0
}
]
]
},
"Check Limit": {
"main": [
[
{
"node": "Limit Reached?",
"type": "main",
"index": 0
}
]
]
},
"Limit Reached?": {
"main": [
[
{
"node": "Wait Until Tomorrow",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Profile",
"type": "main",
"index": 0
}
]
]
},
"Wait Until Tomorrow": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "Get Profile",
"type": "main",
"index": 0
}
]
]
},
"Get Profile": {
"main": [
[
{
"node": "Prepare & Filter",
"type": "main",
"index": 0
}
]
]
},
"Prepare & Filter": {
"main": [
[
{
"node": "Should Send?",
"type": "main",
"index": 0
}
]
]
},
"Should Send?": {
"main": [
[
{
"node": "Send Connection",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 3s",
"type": "main",
"index": 0
}
]
]
},
"Send Connection": {
"main": [
[
{
"node": "Log & Increment",
"type": "main",
"index": 0
}
]
]
},
"Log & Increment": {
"main": [
[
{
"node": "Wait 3s",
"type": "main",
"index": 0
}
]
]
},
"Wait 3s": {
"main": [
[
{
"node": "Loop",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Summary",
"type": "main",
"index": 0
}
]
]
},
"Summary": {
"main": [
[]
]
},
"Loop": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
],
[
{
"node": "Check Limit",
"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.
httpBearerAuthhttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Competitor Poach - Auto Daily Limit. Uses formTrigger, httpRequest. Event-driven trigger; 21 nodes.
Source: https://gist.github.com/connectsafely/a5acae381fdfa159bce028cc9de2d920 — 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 allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)
N8n recently introduced folders and it has been a big improvement on workflow management on top of the tags.
This workflow automates the creation of press releases for music artists releasing a new single. Upload your MP3, fill in basic info, and receive a publication-ready press release saved as a Google Do