This workflow corresponds to n8n.io template #12259 — we link there as the canonical source.
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 →
{
"id": "88oaK8VygviD77nR",
"name": "Generate social hub (link-in-bio) page with FireCrawl AI and Apify",
"tags": [],
"nodes": [
{
"id": "b8a7f53a-e611-4689-92b1-2951f4cf7b94",
"name": "On form submission",
"type": "n8n-nodes-base.formTrigger",
"position": [
560,
-208
],
"parameters": {
"path": "website-link-aggregator",
"options": {},
"formTitle": "Website Link Aggregator",
"formFields": {
"values": [
{
"fieldLabel": "Website URL",
"placeholder": "Example: https://n8n.io",
"requiredField": true
}
]
},
"responseMode": "responseNode",
"formDescription": "Insert your website URL and wait for the result"
},
"typeVersion": 2.1
},
{
"id": "0993afeb-cc7a-4476-a677-96862ae71f05",
"name": "Start Apify Scraper",
"type": "n8n-nodes-base.httpRequest",
"position": [
784,
-208
],
"parameters": {
"url": "=https://api.apify.com/v2/acts/WYyiMAvNXhfc2Rthx/runs?token=YOUR_APIFY_TOKEN",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ Posts: [$json['Website URL']] }) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "3583d1f2-e17c-48b4-94fe-00368ef6e1ea",
"name": "Wait for the Apify Scraper Process",
"type": "n8n-nodes-base.wait",
"position": [
1008,
-208
],
"parameters": {
"amount": 30
},
"typeVersion": 1.1
},
{
"id": "fd7a5122-0de2-4e83-b9d7-6f089c0201cc",
"name": "Get Apify Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
1424,
-288
],
"parameters": {
"url": "={{ \"https://api.apify.com/v2/datasets/\" + $('Start Apify Scraper').item.json.data.defaultDatasetId + \"/items?token=YOUR_APIFY_TOKEN\" }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "9adcd448-b69c-4c86-b1b1-cecfe5c4e538",
"name": "Scrape website description",
"type": "n8n-nodes-base.httpRequest",
"position": [
1424,
-128
],
"parameters": {
"url": "https://api.firecrawl.dev/v1/scrape",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ url: ($('On form submission').item.json['Website URL'].startsWith('http') ? $('On form submission').item.json['Website URL'] : 'https://' + $('On form submission').item.json['Website URL']), formats: ['extract'], extract: { prompt: 'Extract a short 1-2 sentence description about what this business does. Keep it under 150 characters.' } }) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "b2193cfc-1b72-4966-9436-7736bbabc251",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
2528,
-208
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "text/html; charset=utf-8"
}
]
}
},
"respondWith": "text",
"responseBody": "={{ $json.html }}"
},
"typeVersion": 1.1
},
{
"id": "2beb1c73-9831-42eb-ae9d-83ed829d4a8b",
"name": "Merge result from Apify and Firecrawl",
"type": "n8n-nodes-base.merge",
"position": [
1680,
-208
],
"parameters": {},
"typeVersion": 3
},
{
"id": "89827724-2a4b-4968-b0f5-5dd5ac32e6e9",
"name": "Create html format",
"type": "n8n-nodes-base.code",
"position": [
2256,
-208
],
"parameters": {
"jsCode": "const d = $input.first().json;\n\nconst socialIcons = {\n facebook: 'https://cdn.simpleicons.org/facebook/1877F2',\n instagram: 'https://cdn.simpleicons.org/instagram/E1306C',\n twitter: 'https://cdn.simpleicons.org/x/000000',\n linkedin: 'https://www.svgrepo.com/show/157006/linkedin.svg',\n youtube: 'https://cdn.simpleicons.org/youtube/FF0000',\n tiktok: 'https://cdn.simpleicons.org/tiktok/000000',\n whatsapp: 'https://cdn.simpleicons.org/whatsapp/25D366'\n};\n\nlet linksHtml = '';\nconst socials = ['facebook', 'instagram', 'twitter', 'linkedin', 'youtube', 'tiktok', 'whatsapp'];\n\nfor (let i = 0; i < socials.length; i++) {\n const platform = socials[i];\n \n if (d[platform]) {\n const displayName = platform.charAt(0).toUpperCase() + platform.slice(1);\n \n linksHtml += `\n <a href=\"${d[platform]}\" class=\"link\" target=\"_blank\" rel=\"noopener\">\n <img src=\"${socialIcons[platform]}\" class=\"icon\" alt=\"${displayName}\">\n ${displayName}\n </a>`;\n }\n}\n\nlet websiteUrl = d.originalUrl;\nif (!websiteUrl.startsWith('http')) {\n websiteUrl = 'https://' + websiteUrl;\n}\n\nlinksHtml += `\n<a href=\"${websiteUrl}\" class=\"link\" target=\"_blank\" rel=\"noopener\">\n <img src=\"https://cdn.simpleicons.org/googlechrome/4285F4\" class=\"icon\" alt=\"Website\">\n Visit Website\n</a>`;\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${d.domain}</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n \n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n padding: 40px 20px;\n }\n \n .wrapper {\n max-width: 600px;\n margin: 0 auto;\n }\n \n .header {\n text-align: center;\n margin-bottom: 35px;\n }\n \n .profile-pic {\n width: 96px;\n height: 96px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n border: 4px solid white;\n border-radius: 50%;\n margin: 0 auto 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 36px;\n font-weight: 700;\n box-shadow: 0 8px 16px rgba(0,0,0,0.1);\n }\n \n .domain-name {\n font-size: 24px;\n font-weight: 700;\n color: white;\n margin: 0 0 8px 0;\n text-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n \n .bio {\n font-size: 15px;\n line-height: 1.5;\n color: rgba(255,255,255,0.95);\n max-width: 520px;\n margin: 0 auto;\n }\n \n .links-container {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n \n .link {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 18px;\n background: white;\n border-radius: 12px;\n text-decoration: none;\n color: #222;\n font-size: 16px;\n font-weight: 600;\n box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n \n .link:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n }\n \n .link:active {\n transform: translateY(0);\n }\n \n .icon {\n width: 24px;\n height: 24px;\n }\n \n @media (max-width: 500px) {\n body {\n padding: 30px 16px;\n }\n \n .profile-pic {\n width: 80px;\n height: 80px;\n font-size: 30px;\n }\n \n .domain-name {\n font-size: 20px;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"wrapper\">\n <div class=\"header\">\n <div class=\"profile-pic\">${d.initials}</div>\n <h1 class=\"domain-name\">${d.domain}</h1>\n <p class=\"bio\">${d.description}</p>\n </div>\n \n <div class=\"links-container\">\n ${linksHtml}\n </div>\n </div>\n</body>\n</html>`;\n\nreturn { html };\n"
},
"typeVersion": 2
},
{
"id": "58442908-f5cc-496c-9372-34dc76620889",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
496,
-336
],
"parameters": {
"color": 7,
"width": 704,
"height": 336,
"content": "## 1. Start scraping process from the user's input"
},
"typeVersion": 1
},
{
"id": "cbdc29e4-d7c3-402a-bf1b-6c54391558dd",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1328,
-368
],
"parameters": {
"color": 7,
"width": 768,
"height": 432,
"content": "## 2. Get the scrape results and start Firecrawl to summarize"
},
"typeVersion": 1
},
{
"id": "4df92277-ad39-4901-94ef-5eb1db614a2f",
"name": "Process the description and all social media",
"type": "n8n-nodes-base.code",
"position": [
1888,
-208
],
"parameters": {
"jsCode": "const allInputs = $input.all();\nconst originalUrl = $('On form submission').item.json['Website URL'];\n\nconst apifyItems = allInputs.filter(item => item.json['Contact Type']);\nconst firecrawlItems = allInputs.filter(item => item.json.data && item.json.data.extract);\n\nlet description = 'Connect with us through our channels';\nif (firecrawlItems.length > 0) {\n const extract = firecrawlItems[0].json.data.extract;\n if (extract.businessDescription) {\n description = extract.businessDescription;\n } else if (typeof extract === 'string') {\n description = extract;\n } else if (extract.description || extract.summary) {\n description = extract.description || extract.summary;\n }\n if (description.length > 150) {\n description = description.substring(0, 147) + '...';\n }\n}\n\nlet emails = [];\nlet phones = [];\nlet socials = [];\n\napifyItems.forEach(input => {\n const type = input.json['Contact Type'];\n const value = input.json['Contact Value'];\n if (!type || !value) return;\n \n const t = type.toLowerCase().trim();\n if (t.includes('email')) emails.push(value);\n else if (t.includes('phone')) phones.push(value);\n else if (t.includes('social')) socials.push(value);\n});\n\nemails = [...new Set(emails)].filter(e => e && !e.toLowerCase().includes('noreply')).slice(0, 3);\nphones = [...new Set(phones)].filter(p => p && p.length > 5).slice(0, 3);\n\nconst socialMap = {};\nsocials.forEach(url => {\n if (!url || typeof url !== 'string') return;\n \n const u = url.toLowerCase();\n let platform = null;\n \n if (u.includes('facebook.com') || u.includes('fb.com')) platform = 'Facebook';\n else if (u.includes('instagram.com')) platform = 'Instagram';\n else if (u.includes('twitter.com') || u.includes('x.com')) platform = 'Twitter';\n else if (u.includes('linkedin.com')) platform = 'LinkedIn';\n else if (u.includes('youtube.com') || u.includes('youtu.be')) platform = 'YouTube';\n else if (u.includes('tiktok.com')) platform = 'TikTok';\n else if (u.includes('whatsapp.com') || u.includes('wa.me')) platform = 'WhatsApp';\n \n if (platform && !socialMap[platform]) socialMap[platform] = url;\n});\n\nlet domain = 'Website';\ntry {\n const url = new URL(originalUrl.startsWith('http') ? originalUrl : 'https://' + originalUrl);\n domain = url.hostname.replace('www.', '');\n} catch(e) {\n domain = originalUrl;\n}\n\nconst initials = domain.split('.')[0].substring(0, 2).toUpperCase();\n\nreturn {\n domain,\n initials,\n originalUrl,\n description,\n emails,\n phones,\n facebook: socialMap['Facebook'] || '',\n instagram: socialMap['Instagram'] || '',\n twitter: socialMap['Twitter'] || '',\n linkedin: socialMap['LinkedIn'] || '',\n youtube: socialMap['YouTube'] || '',\n tiktok: socialMap['TikTok'] || '',\n whatsapp: socialMap['WhatsApp'] || ''\n};\n"
},
"typeVersion": 2
},
{
"id": "3cb0f863-94e5-4677-ab72-ae8a8ffd9918",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2192,
-368
],
"parameters": {
"color": 7,
"width": 544,
"height": 432,
"content": "## 3. Create HTML and send back to the webhook (Form)"
},
"typeVersion": 1
},
{
"id": "4ef13fb1-fb33-42a4-852b-186929728f1a",
"name": "View HTML for redesign or debug",
"type": "n8n-nodes-base.html",
"position": [
2432,
272
],
"parameters": {
"html": "{{ $json.html }}"
},
"typeVersion": 1.2
},
{
"id": "d9cba68e-7cdd-4086-bdaa-c759e85e7102",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2208,
160
],
"parameters": {
"color": 7,
"width": 528,
"height": 304,
"content": "## Use this node to debug or show the HTML if want to edit the style"
},
"typeVersion": 1
},
{
"id": "bd6b274e-9ae3-4e68-9e73-7f7ae9095eb7",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-112,
-512
],
"parameters": {
"width": 512,
"height": 704,
"content": "## Generate social hub (link-in-bio) page with FireCrawl AI and Apify\n\n### How it works\n1. User submits a website URL through the form.\n2. Apify scrapes contact info \u00e2\u20ac\u201d emails, phones, and social media links.\n3. FireCrawl AI generates a short business description via LLM extraction.\n4. Results are merged and social platforms are identified (Facebook, Instagram, X, LinkedIn, YouTube, TikTok, WhatsApp).\n5. A styled link-in-bio HTML page is generated and returned as the form response.\n\n### Setup steps\n1. Get an [Apify API key](https://apify.com) and a [FireCrawl API key](https://www.firecrawl.dev).\n2. Replace `YOUR_APIFY_TOKEN` in both the **Start Apify Scraper** and **Get Apify Results** nodes.\n3. Replace `YOUR_TOKEN_HERE` in the **Scrape website description** node's Authorization header with your FireCrawl key (format: `Bearer fc-xxx`).\n4. If Apify errors on first run, visit the [Contact Details Scraper Actor](https://console.apify.com/actors/WYyiMAvNXhfc2Rthx/input) and run it once manually to enable it.\n\n### Customize\n1. **Change HTML styling**: Edit the CSS in the \\\"Create html format\\\" node to customize colors, fonts, layout, or add animations. The current design uses a purple gradient background with white cards.\n2. **Debug HTML output**: Use the \\\"View HTML for redesign or debug\\\" node at the bottom to preview the generated HTML without submitting through the webhook.\n"
},
"typeVersion": 1
},
{
"id": "d973bf35-c9df-4c3b-9bd4-56c13f413fde",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
2832,
-464
],
"parameters": {
"width": 528,
"height": 652,
"content": "## Final result\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "732c712a-9c30-437d-b303-28a6d2288777",
"connections": {
"Get Apify Results": {
"main": [
[
{
"node": "Merge result from Apify and Firecrawl",
"type": "main",
"index": 0
}
]
]
},
"Create html format": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"On form submission": {
"main": [
[
{
"node": "Start Apify Scraper",
"type": "main",
"index": 0
}
]
]
},
"Start Apify Scraper": {
"main": [
[
{
"node": "Wait for the Apify Scraper Process",
"type": "main",
"index": 0
}
]
]
},
"Scrape website description": {
"main": [
[
{
"node": "Merge result from Apify and Firecrawl",
"type": "main",
"index": 1
}
]
]
},
"Wait for the Apify Scraper Process": {
"main": [
[
{
"node": "Get Apify Results",
"type": "main",
"index": 0
},
{
"node": "Scrape website description",
"type": "main",
"index": 0
}
]
]
},
"Merge result from Apify and Firecrawl": {
"main": [
[
{
"node": "Process the description and all social media",
"type": "main",
"index": 0
}
]
]
},
"Process the description and all social media": {
"main": [
[
{
"node": "Create html format",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n template demonstrates how to create a link-in-bio style landing page (similar to Linktree or Beacons.ai) that automatically aggregates all social media links from any website.
Source: https://n8n.io/workflows/12259/ — 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 uses the Zyte API to automatically detect and extract structured data from E-commerce sites, Articles, Job Boards, and Search Engine Results (SERP) - no custom CSS selectors required.
Automate LinkedIn lead generation by scraping comments from targeted posts and enriching profiles with detailed data
This n8n workflow collects leads from Google Maps, scrapes their websites via direct HTTP requests, and extracts valid email addresses — all while mimicking real user behavior to improve scraping reli
This workflow automates lead generation by scraping business data from Google Maps using Apify, enriching it with verified email addresses via Anymailfinder, and storing the results in a NocoDB databa
This workflow automates the process of extracting and qualifying leads from LinkedIn post comments based on your Ideal Customer Profile (ICP) criteria. It turns LinkedIn engagement into a structured,