This workflow transforms competitive intelligence by automatically capturing screenshots of competitor websites and testing them against AI personas representing your target market. Starting with a list of competitors in Google Sheets, it systematically captures high-quality screenshots using urlscan.io, then presents all competitor landing pages simultaneously to Rally's AI audience in a voting scenario. The personas evaluate which competitor would be most appealing from a prospect's perspective, providing quantified insights into competitive positioning and visual appeal with detailed reasoning and vote percentages.
Competitor Analysis

Automatically capture competitor website screenshots and test them against AI personas to determine which landing pages would be most appealing to your target audience. Get quantified competitor performance data with vote analysis and reasoning.

Click to expand
Overview
π― Pro Tips & Secret Sauce
The magic lies in the automated visual competitive intelligence with AI prospect simulation:
- Systematic Screenshot Automation - Uses urlscan.io's professional scanning service to capture high-quality, standardized screenshots of competitor websites, eliminating manual screenshot collection and ensuring consistent visual representation
- Prospect Perspective Testing - AI personas don't just analyze design quality; they simulate actual decision-making scenarios where prospects compare landing pages side-by-side, mimicking real competitive evaluation behavior
- Intelligent Vote Correlation - Custom logic matches Rally's letter-based voting responses back to specific competitor domains, calculating precise market appeal percentages and ranking competitors by visual effectiveness
- Automated Competitive Tracking - Seamlessly integrates with Google Sheets for competitor list management and results storage, creating a repeatable process for ongoing competitive monitoring and trend analysis
- Quantified Visual Appeal Analysis - Transforms subjective "this looks better" opinions into objective data with vote counts, percentages, and rankings that can drive strategic decisions about positioning and design direction
This creates a comprehensive competitive intelligence system that reveals not just what competitors are doing, but how effective their visual positioning is likely to be with your target audience, providing actionable insights for strategic positioning and design optimization.
π Step-by-Step Instructions
- Manual Trigger - User initiates the competitive analysis workflow to begin systematic evaluation of competitor websites
- Read Competitors - Automatically pulls competitor information from a designated Google Sheets document containing company names and homepage URLs
- Process URLs - Custom code filters and validates competitor data, cleaning URLs and ensuring proper formatting while skipping invalid or incomplete entries
- Take Screenshots - Systematically captures high-quality screenshots of each competitor website using urlscan.io's professional scanning service with consistent viewport settings
- Wait for Scan - Introduces a 30-second delay to allow urlscan.io to complete the comprehensive website analysis and screenshot generation
- Get Scan Results - Retrieves the completed scan data and screenshot URLs from urlscan.io using the unique scan UUIDs
- Extract Screenshot URLs - Processes the scan results to extract clean, publicly accessible screenshot URLs for each competitor website
- Aggregate Data - Combines all competitor domains and their corresponding screenshot URLs into a structured dataset for Rally analysis
- Analyze with Rally - Presents all competitor screenshots simultaneously to Rally's AI personas, asking them to vote on which vendor would be most appealing from a prospect's perspective
- Calculate Results - Processes Rally's voting responses to tally votes for each competitor, calculate percentages, and rank competitors by appeal with detailed vote breakdown
- Save to Sheets - Exports the comprehensive analysis results to Google Sheets with competitor names, vote counts, percentages, and timestamps for ongoing tracking
π Requirements
Required Integrations
- Google Sheets - Competitor list management and results storage with structured data organization
- urlscan.io - Professional website scanning and screenshot capture service with high-quality visual output
- Rally API - AI persona testing service with visual analysis capabilities and voting mode functionality
- Manual Trigger - On-demand execution for periodic competitive analysis sessions
Required Credentials
- Google Sheets OAuth2 credentials with read/write access to competitor tracking spreadsheets
- urlscan.io API key with scanning permissions and sufficient quota for batch operations
- Rally API Bearer token with voting mode access and visual file upload capabilities
Setup Prerequisites
- Google Sheets document with competitor data in structured format (company_name, homepage_url columns)
- urlscan.io account with active subscription for batch scanning operations
- Rally account with configured audience personas matching your target market (default: synthetic research audience)
- Competitor URLs should be accessible and properly formatted with valid domains
π n8n Workflow Template
{
"active": false,
"connections": {
"Aggregate": {
"main": [
[
{
"index": 0,
"node": "Analyze with Rally",
"type": "main"
}
]
]
},
"Analyze with Rally": {
"main": [
[
{
"index": 0,
"node": "Code1",
"type": "main"
}
]
]
},
"Code": {
"main": [
[
{
"index": 0,
"node": "Take Screenshot",
"type": "main"
}
]
]
},
"Code1": {
"main": [
[
{
"index": 0,
"node": "Save to Sheets",
"type": "main"
}
]
]
},
"Extract Screenshot URL": {
"main": [
[
{
"index": 0,
"node": "Aggregate",
"type": "main"
}
]
]
},
"Get Scan Result": {
"main": [
[
{
"index": 0,
"node": "Extract Screenshot URL",
"type": "main"
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"index": 0,
"node": "Read Competitors",
"type": "main"
}
]
]
},
"Read Competitors": {
"main": [
[
{
"index": 0,
"node": "Code",
"type": "main"
}
]
]
},
"Take Screenshot": {
"main": [
[
{
"index": 0,
"node": "Wait for Scan",
"type": "main"
}
]
]
},
"Wait for Scan": {
"main": [
[
{
"index": 0,
"node": "Get Scan Result",
"type": "main"
}
]
]
}
},
"id": "Lpeez4DEb18VDztM",
"meta": {
"instanceId": "7aa2e96d57ff40383569724f8ecb13d674a87bf09d39aa5d8fa5ba31f7a8407a",
"templateCredsSetupCompleted": true
},
"name": "Competitor Analysis",
"nodes": [
{
"id": "e80802e1-bb5c-4462-bd77-8c191988f80f",
"name": "Manual Trigger",
"parameters": {},
"position": [
0,
0
],
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1
},
{
"credentials": {
"googleSheetsOAuth2Api": {
"id": "D3gzSdLQUA7CCmVX",
"name": "Google Sheets account"
}
},
"id": "cb8955e4-4c5d-446e-897b-26bfaf865c6f",
"name": "Read Competitors",
"parameters": {
"documentId": {
"__rl": true,
"mode": "id",
"value": "1rB_Zk4NsGMuxxkMLLwVlBdEPmTbzEaYek7WsmJeH4uo"
},
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet1"
}
},
"position": [
200,
0
],
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.6
},
{
"credentials": {
"urlScanIoApi": {
"id": "Im8iYAdzyQ86pMp8",
"name": "urlscan.io account"
}
},
"id": "a67d14c9-f1b1-4592-89f6-7dca5b8b0a14",
"name": "Take Screenshot",
"parameters": {
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"jsonBody": "={\n \"url\": \"{{ $(\u0027Code\u0027).item.json.website_url.trim() }}\",\n \"visibility\": \"public\",\n \"tags\": [\"competitor-analysis\"]\n}",
"method": "POST",
"nodeCredentialType": "urlScanIoApi",
"options": {},
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"url": "https://urlscan.io/api/v1/scan/"
},
"position": [
640,
0
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "113422c3-7efe-48d7-af21-a1284823ff1c",
"name": "Wait for Scan",
"parameters": {
"amount": 30
},
"position": [
880,
0
],
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"webhookId": "ce56afe8-ef3d-4f36-9b15-cb1c829f8cd4"
},
{
"credentials": {
"urlScanIoApi": {
"id": "Im8iYAdzyQ86pMp8",
"name": "urlscan.io account"
}
},
"id": "7b86aba2-61ea-4d85-95cc-bcf385276d4b",
"name": "Get Scan Result",
"parameters": {
"authentication": "predefinedCredentialType",
"nodeCredentialType": "urlScanIoApi",
"options": {},
"url": "={{ \u0027https://urlscan.io/api/v1/result/\u0027 + $(\u0027Take Screenshot\u0027).item.json.uuid + \u0027/\u0027 }}"
},
"position": [
1100,
0
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "261a140e-ca2a-4918-aed5-5797f8e8941d",
"name": "Extract Screenshot URL",
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "screenshot_url",
"type": "string",
"value": "={{ $json.task.screenshotURL }}"
},
{
"id": "2",
"name": "scan_uuid",
"type": "string",
"value": "={{ $(\u0027Take Screenshot\u0027).item.json.uuid }}"
}
]
},
"includeOtherFields": true,
"options": {}
},
"position": [
1280,
0
],
"type": "n8n-nodes-base.set",
"typeVersion": 3.4
},
{
"credentials": {
"httpBearerAuth": {
"id": "HeUQ9K9oUtQ5TyHr",
"name": "Bearer Auth account"
}
},
"id": "f01a6716-ff6b-4b8a-8ecd-2b9662b00e31",
"name": "Analyze with Rally",
"parameters": {
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"jsonBody": "={\n \"smart\": false,\n \"provider\": \"google\",\n \"query\": \"You are in the market for synthetic research, and you arrive at these landing pages for the following vendors. Which would you be most interested in?\\\\n\\\\n{{ $json.domain.map((domain, index) =\u003e String.fromCharCode(97 + index) + \u0027) \u0027 + domain).join(\u0027\\\\n\u0027) }}\",\n \"audience_id\": \"ra6832c53468b43\",\n \"voting_mode\": true,\n \"files\": {{ JSON.stringify($json.screenshot_url.map(url =\u003e ({ url: url, type: \"image/png\" }))) }}\n}",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"url": "https://api.askrally.com/api/v1/chat"
},
"position": [
560,
260
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"credentials": {
"googleSheetsOAuth2Api": {
"id": "D3gzSdLQUA7CCmVX",
"name": "Google Sheets account"
}
},
"id": "920914d2-b86f-4bb7-a55f-db33b647799a",
"name": "Save to Sheets",
"parameters": {
"columns": {
"attemptToConvertTypes": false,
"convertFieldsToString": false,
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"schema": [
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "option",
"id": "option",
"removed": false,
"required": false,
"type": "string"
},
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "vendor",
"id": "vendor",
"removed": false,
"required": false,
"type": "string"
},
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "votes",
"id": "votes",
"removed": false,
"required": false,
"type": "string"
},
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "percentage",
"id": "percentage",
"removed": false,
"required": false,
"type": "string"
},
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "total_responses",
"id": "total_responses",
"removed": false,
"required": false,
"type": "string"
},
{
"canBeUsedToMatch": true,
"defaultMatch": false,
"display": true,
"displayName": "timestamp",
"id": "timestamp",
"removed": false,
"required": false,
"type": "string"
}
],
"value": {}
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "1rB_Zk4NsGMuxxkMLLwVlBdEPmTbzEaYek7WsmJeH4uo"
},
"operation": "append",
"options": {
"useAppend": false
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
}
},
"position": [
1000,
260
],
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.6
},
{
"id": "f890604a-4b1b-455d-9ad9-c5a2179937ae",
"name": "Code",
"parameters": {
"jsCode": "const item = $input.item.json;\n\n// Skip rows where is_rally is true (if you want to skip Rally rows)\nif (item.is_rally === true) {\n console.log(`Skipping Rally row: ${item.company_name}`);\n return null;\n}\n\n// Get the data from your specific columns\nconst competitorName = item.company_name;\nconst websiteUrl = item.homepage_url;\n\n// Skip if missing essential data\nif (!competitorName || !websiteUrl) {\n console.log(`Skipping row - missing data. Company: \"${competitorName}\", URL: \"${websiteUrl}\"`);\n return null;\n}\n\n// Clean the URL if needed\nlet cleanUrl = websiteUrl.toString().trim();\n\n// Ensure URL has protocol\nif (!cleanUrl.startsWith(\u0027http://\u0027) \u0026\u0026 !cleanUrl.startsWith(\u0027https://\u0027)) {\n cleanUrl = \u0027https://\u0027 + cleanUrl;\n}\n\nconsole.log(`Processing: ${competitorName} -\u003e ${cleanUrl}`);\n\nreturn {\n json: {\n competitor_name: competitorName,\n website_url: cleanUrl,\n notes: item.notes || \u0027\u0027,\n original_data: item\n }\n};",
"mode": "runOnceForEachItem"
},
"position": [
420,
0
],
"type": "n8n-nodes-base.code",
"typeVersion": 2
},
{
"id": "042cadcb-b3ec-49dc-8527-324d4898eb0b",
"name": "Aggregate",
"parameters": {
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "page.domain"
},
{
"fieldToAggregate": "screenshot_url"
}
]
},
"options": {}
},
"position": [
340,
260
],
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1
},
{
"id": "95f067f2-0f20-4111-a5ad-6444a7dbdd1c",
"name": "Code1",
"parameters": {
"jsCode": "let optionCounts = {};\nlet total = 0;\n\n// Count option selections from Rally responses\nfor (const item of $input.all()) {\n try {\n // Access the responses array from Rally data\n const responses = item.json.responses || [];\n \n for (const responseItem of responses) {\n const responseData = JSON.parse(responseItem.response);\n const option = responseData.option?.toLowerCase();\n \n if (option) {\n total++;\n optionCounts[option] = (optionCounts[option] || 0) + 1;\n }\n }\n } catch (error) {\n console.log(`Error parsing Rally response:`, error.message);\n }\n}\n\n// Get vendor domains from Aggregate node\nlet domains = [];\ntry {\n domains = $(\u0027Aggregate\u0027).first().json.domain || [];\n} catch (error) {\n console.log(\"Error retrieving domains from Aggregate node:\", error.message);\n}\n\n// Generate timestamp for this analysis\nconst timestamp = new Date().toISOString();\n\n// Combine vote counts with vendor domains\nconst results = [];\nfor (const [option, count] of Object.entries(optionCounts)) {\n const index = option.charCodeAt(0) - 97; // a = 0, b = 1, c = 2...\n const vendor = domains[index] || `[Unknown option \"${option}\"]`;\n const percentage = total \u003e 0 ? (count / total * 100).toFixed(1) : \"0.0\";\n \n results.push({\n option,\n vendor,\n votes: count,\n percentage: parseFloat(percentage),\n total_responses: total,\n timestamp: timestamp\n });\n}\n\n// Sort by most votes\nresults.sort((a, b) =\u003e b.votes - a.votes);\n\nconsole.log(`\\nTotal responses: ${total}`);\nconsole.log(`Analysis timestamp: ${timestamp}`);\nconsole.log(`Vote breakdown:`);\nresults.forEach(r =\u003e {\n console.log(` ${r.option.toUpperCase()}) ${r.vendor}: ${r.votes} votes (${r.percentage}%)`);\n});\n\n// Return one item per result (for Google Sheets export)\nreturn results.map(r =\u003e ({ json: r }));"
},
"position": [
800,
260
],
"type": "n8n-nodes-base.code",
"typeVersion": 2
}
],
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"tags": [],
"versionId": "b55381dc-36e6-4012-bb12-1cc09c20394e"
}
About the Author

Mike Taylor
Mike Taylor is the CEO & Co-Founder of Rally. He previously co-founded a 50-person growth marketing agency called Ladder, created marketing & AI courses on LinkedIn, Vexpower, and Udemy taken by over 450,000 people, and published a book with OβReilly on prompt engineering.