This workflow corresponds to n8n.io template #9941 — we link there as the canonical source.
This workflow follows the Apifyn8N Nodes Apify → Google Sheets 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": "2N6RJnwzjP8bVcFu",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Upwork Job Alert workflow",
"tags": [],
"nodes": [
{
"id": "b5449656-57b5-40aa-8a83-b3279b11f734",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"position": [
4064,
816
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f86d90d2-d03a-4553-9bc0-820da3f74b56",
"name": "Title",
"type": "string",
"value": "={{ $json.title }}"
},
{
"id": "880e6f53-02c9-47f4-9d15-a2e9f086891f",
"name": "Description",
"type": "string",
"value": "={{ $json.description }}"
},
{
"id": "e098c4db-51f5-4038-811a-8f00c3924475",
"name": "Status",
"type": "string",
"value": "New"
},
{
"id": "e0b12de9-6e31-4405-8aed-82e90b85ed98",
"name": "Payment Type",
"type": "string",
"value": "={{ $json.jobType }}"
},
{
"id": "830a8fec-9b27-47d5-b933-eb0d5b0add9f",
"name": "Experience",
"type": "string",
"value": "={{ $json.experienceLevel }}"
},
{
"id": "8e733eaa-7592-4e86-b66b-5eeaa0e3fc97",
"name": "Salary",
"type": "string",
"value": "={{ $json.budget }}"
},
{
"id": "9e733eaa-7592-4e86-b66b-5eeaa0e3fc98",
"name": "Location",
"type": "string",
"value": "={{ $json.clientLocation }}"
},
{
"id": "ce733eaa-7592-4e86-b66b-5eeaa0e3fca1",
"name": "URL",
"type": "string",
"value": "={{ $json.url }}"
},
{
"id": "de733eaa-7592-4e86-b66b-5eeaa0e3fca2",
"name": "Date",
"type": "string",
"value": "={{ $json.absoluteDate }}"
},
{
"id": "ee733eaa-7592-4e86-b66b-5eeaa0e3fca3",
"name": "Found",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "54646432-f8fe-4c14-9976-c151f16c9a86",
"name": "hire_percentage",
"type": "string",
"value": "={{ $json.clientHireRatePercent }}"
},
{
"id": "fae26632-6c73-42cc-b7da-90ae8c757d4d",
"name": "spending_history",
"type": "string",
"value": "={{ $json.clientTotalSpent }}"
},
{
"id": "76f9d4ee-2a39-478d-9955-9cd1df47d8c4",
"name": "rating",
"type": "string",
"value": "={{ $json.clientRating }}"
},
{
"id": "492ee934-f629-4ffe-8f6f-63bed6cc4f9f",
"name": "score",
"type": "string",
"value": "={{ $json.score }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9672eb74-4c5a-439b-aab5-4e06663a937f",
"name": "Send message",
"type": "n8n-nodes-base.whatsApp",
"position": [
4576,
816
],
"parameters": {
"textBody": "=# New Job Alet make the Proposal ASAP\n\nName : \n\nJob Title : {{ $json['Job Tile'] }}\n\nPosted Date : {{ $json['job posted'] }}\n\nHourly Budget : {{ $json.budget }}\n\nPayment Type : {{ $json['Payment type'] }}\n",
"operation": "send",
"phoneNumberId": "745077835357667",
"additionalFields": {
"previewUrl": false
},
"recipientPhoneNumber": "Add your number"
},
"credentials": {
"whatsAppApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c49b734d-ba70-41cf-bbe6-3b9ddc2b3472",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
624
],
"parameters": {
"color": 3,
"width": 1376,
"height": 400,
"content": "# Freelance Job Tracker with WhatsApp Alerts"
},
"typeVersion": 1
},
{
"id": "847030db-1bc7-424a-b73b-c736109da905",
"name": "Run an Actor",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
3584,
816
],
"parameters": {
"actorId": {
"__rl": true,
"mode": "id",
"value": "Add your Actor Id"
},
"timeout": {},
"customBody": "={\n \n \"paymentVerified\": true,\n \"query\": \"{{ $json.chatInput }}\"\n}"
},
"credentials": {
"apifyApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "dfb38ee0-cb7e-4c6a-ba72-9fc8fa592ec5",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
3904,
816
],
"parameters": {
"jsCode": "// === INPUT ===\n// Expecting keywords already provided by the previous node (like Chat Trigger)\nconst inputData = $input.item.json;\nconst query = inputData.keywords || \"\"; // make sure this field exists\nconst keywords = Array.isArray(query)\n ? query.map(k => k.toLowerCase())\n : query.toLowerCase().split(/\\s+/).filter(k => k.length > 1);\n\n// === PROCESS EACH ITEM ===\nreturn items.map(item => {\n const title = (item.json.title || \"\").toLowerCase();\n const description = (item.json.description || \"\").toLowerCase();\n\n // Combine both fields for scoring\n const text = title + \" \" + description;\n\n // Score relevance\n let score = 0;\n keywords.forEach(word => {\n const regex = new RegExp(\"\\\\b\" + word + \"\\\\b\", \"gi\");\n const matches = text.match(regex);\n if (matches) score += matches.length * 20; // weight per keyword match\n });\n\n // Add extra weight if keyword appears in title\n keywords.forEach(word => {\n const regexTitle = new RegExp(\"\\\\b\" + word + \"\\\\b\", \"gi\");\n const matchesInTitle = title.match(regexTitle);\n if (matchesInTitle) score += matchesInTitle.length * 30;\n });\n\n // Normalize score\n if (score > 100) score = 100;\n\n // Attach score\n item.json.score = score;\n\n return item;\n});\n"
},
"typeVersion": 2
},
{
"id": "fc856cc0-beb8-4b31-89df-cd26bb295e49",
"name": "Append or update row in sheet1",
"type": "n8n-nodes-base.googleSheets",
"position": [
4224,
816
],
"parameters": {
"columns": {
"value": {
"URL": "={{ $json.URL }}",
"Date": "={{ $json.Date }}",
"Found": "={{ $json.Found }}",
"Score": "={{ $json.score }}",
"Title": "={{ $json.Title }}",
"Salary": "={{ $json.Salary }}",
"Status": "={{ $json.Status }}",
"Location": "={{ $json.Location }}",
"Experience": "={{ $json.Experience }}",
"Description": "={{ $json.Description }}",
"Payment Type": "={{ $json['Payment Type'] }}"
},
"schema": [
{
"id": "Title",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Payment Type",
"type": "string",
"display": true,
"required": false,
"displayName": "Payment Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Experience",
"type": "string",
"display": true,
"required": false,
"displayName": "Experience",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Salary",
"type": "string",
"display": true,
"required": false,
"displayName": "Salary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Location",
"type": "string",
"display": true,
"required": false,
"displayName": "Location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Score",
"type": "string",
"display": true,
"required": false,
"displayName": "Score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"display": true,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Found",
"type": "string",
"display": true,
"required": false,
"displayName": "Found",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Title"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "Add your sheet url",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1rBRKpiA2COarqpmYTAR1pl2pOtj4l3ftBEjoNc6lU_A",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1rBRKpiA2COarqpmYTAR1pl2pOtj4l3ftBEjoNc6lU_A/edit?usp=drivesdk",
"cachedResultName": "Upwork Whatsapp alert"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "2e803289-97da-41b5-98cc-d91c985b7d1b",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
3440,
816
],
"parameters": {
"options": {}
},
"typeVersion": 1.3
},
{
"id": "9b24fdab-a2f2-4449-ac11-81cc3d84ba6d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2464,
624
],
"parameters": {
"width": 880,
"height": 1152,
"content": "## \ud83d\udcbc Freelance Job Tracker with WhatsApp Alerts (Apify + n8n)\n\n## \ud83d\udfe8 Workflow Overview\n\nThis workflow automates freelance job tracking by using Apify Actors to scrape job listings (e.g., from Upwork or Fiverr) and sends real-time WhatsApp alerts when new opportunities are found.\nIt\u2019s designed for freelancers, agencies, and solopreneurs who want to stay updated without constantly checking job sites.\n\n\ud83e\udde9 Sticky Notes Summary\n\n\ud83d\udfe9 1. When Chat Message Received (Trigger)\n\nThis node listens for a message in WhatsApp (via the connected API or webhook).\nYou can also trigger it manually to start the workflow when needed.\n\n\ud83d\udfe9 2. Run Apify Actor\n\nRuns your Apify Actor that scrapes freelance job listings from platforms like Upwork, Freelancer, or Fiverr.\nMake sure your Apify API token is connected in credentials and that your actor ID matches the one from your Apify dashboard.\n\n\ud83d\udfe9 3. Get Dataset Items\n\nRetrieves the latest dataset items (scraped job listings) from the Apify run.\nEach dataset item usually contains job title, link, description, and price/rate.\n\n\ud83d\udfe9 4. Code (JavaScript)\n\nThis node filters and formats the dataset results.\nExample: Only include jobs that contain specific keywords like automation, Python, or n8n.\nIt also limits the number of alerts to avoid message spam.\n\n\ud83d\udfe9 5. Edit Fields\n\nMaps the cleaned data (title, budget, URL) into proper fields for further processing.\nThis ensures consistent message formatting for both Sheets and WhatsApp.\n\n\ud83d\udfe9 6. Append or Update Row in Google Sheet\n\nLogs each new job found into your Google Sheet for record-keeping.\nIf a job already exists (based on URL), it updates instead of duplicating \u2014 keeping your sheet clean.\n\n\ud83d\udfe9 7. Edit Fields (Prepare Message)\n\nPrepares the message text for WhatsApp.\nExample:\n\n\ud83d\udd14 New Job Alert!\n\ud83d\udcbc {{job_title}}\n\ud83d\udcb0 Budget: {{price}}\n\ud83d\udd17 Link: {{url}}\n\n\n\ud83d\udfe9 8. Send WhatsApp Message\n\nSends the formatted alert message directly to your WhatsApp using your WhatsApp API or Twilio integration.\nThis is the final step that keeps you instantly informed when new jobs are posted."
},
"typeVersion": 1
},
{
"id": "256ef1af-d543-4bab-af85-6c6a9a891849",
"name": "Edit Fields To send Specific Data",
"type": "n8n-nodes-base.set",
"position": [
4400,
816
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d78912c8-c63e-4f32-af19-60a568d02377",
"name": "Job Tile",
"type": "string",
"value": "={{ $json.Title }}"
},
{
"id": "7ade5447-cac2-4fc2-bff2-517a8583d11e",
"name": "job posted",
"type": "string",
"value": "={{ $json.Date }}"
},
{
"id": "caff77ba-6c86-4685-b752-bae8ed41e58f",
"name": "Payment type",
"type": "string",
"value": "={{ $json['Payment Type'] }}"
},
{
"id": "52215059-aa70-469b-8b09-f51ada9a5fa0",
"name": "score",
"type": "string",
"value": "={{ $json.Score }}"
},
{
"id": "89926694-3595-4d22-b6ea-00228fb38a11",
"name": "budget",
"type": "string",
"value": "={{ $json.Salary }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "01b532f0-cf78-4ffc-8998-f01878224a45",
"name": "Get dataset items",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
3744,
816
],
"parameters": {
"resource": "Datasets",
"datasetId": "={{ $json.defaultDatasetId }}"
},
"credentials": {
"apifyApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5107bd3a-5ecd-4947-84e9-4435b5b53b50",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3440,
1040
],
"parameters": {
"width": 1088,
"height": 800,
"content": "# \ud83e\udde0 Who It\u2019s For\n\n## 1-Freelancers and solopreneurs looking for instant job alerts\n\n## 2-Agencies monitoring job boards for leads\n\n## 3-Developers wanting to automate freelance opportunity tracking\n\n# \u2699\ufe0f Requirements\n\n## 1-Apify account (with an active Actor for job scraping)\n\n## 2-WhatsApp API / Twilio setup for message delivery\n\n## 3-Google Sheets account for tracking job logs\n\n## 4-n8n Cloud or self-hosted instance\n\n# \ud83d\udca1 Pro Tip\n\n## You can schedule this workflow to run every few hours using the Cron node \u2014 it will automatically check for new jobs and send alerts even when you\u2019re offline."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "47345b24-2f98-441d-b460-fcde3f1559ca",
"connections": {
"Edit Fields1": {
"main": [
[
{
"node": "Append or update row in sheet1",
"type": "main",
"index": 0
}
]
]
},
"Run an Actor": {
"main": [
[
{
"node": "Get dataset items",
"type": "main",
"index": 0
}
]
]
},
"Get dataset items": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Edit Fields1",
"type": "main",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "Run an Actor",
"type": "main",
"index": 0
}
]
]
},
"Append or update row in sheet1": {
"main": [
[
{
"node": "Edit Fields To send Specific Data",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields To send Specific Data": {
"main": [
[
{
"node": "Send message",
"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.
apifyApigoogleSheetsOAuth2ApiwhatsAppApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automatically track new freelance job postings from any platform using Apify Actors, process the results, and get real-time WhatsApp alerts for new opportunities. This workflow saves jobs to Google Sheets for record-keeping and sends instant notifications so you never miss a…
Source: https://n8n.io/workflows/9941/ — 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 comprehensive N8N automation template revolutionizes content creation by delivering a complete end-to-end solution for AI-powered blog generation. Transform simple ideas into fully SEO-optimized,
This template is perfect for: AI art enthusiasts who want to stay updated on trending AI-generated artwork Content curators looking to automate art discovery Japanese-speaking users who want translate
End-to-End Video Creation from user idea or transcript AI-Powered Scriptwriting using LLMs (e.g., DeepSeek via OpenRouter) Voiceover Generation with customizable TTS voices Image Scene Generation usin
Get notified when the International Space Station passes over your location - but only when you can actually see it! This workflow combines real-time ISS tracking with weather condition checks to send
This advanced n8n workflow is designed for SEO specialists, digital agency owners, webmasters, and marketing managers who need a comprehensive, automated solution to track and improve their website's