This workflow corresponds to n8n.io template #17289 — we link there as the canonical source.
This workflow follows the Datatable → 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": "f5x0V0gia7wOWWUL",
"name": "Vinted New-Listing Alerts to Telegram",
"tags": [],
"nodes": [
{
"id": "50000000-0000-0000-4000-8000-000000000001",
"name": "Manual Start Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1840,
224
],
"parameters": {},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000002",
"name": "Hourly Alert Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1840,
16
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "50000000-0000-0000-4000-8000-000000000003",
"name": "Create Delivery Ledger",
"type": "n8n-nodes-base.dataTable",
"position": [
-1568,
112
],
"parameters": {
"columns": {
"column": [
{
"name": "workflowSlug"
},
{
"name": "itemKey"
},
{
"name": "destination"
},
{
"name": "deliveredAt",
"type": "date"
}
]
},
"options": {
"createIfNotExists": true
},
"resource": "table",
"operation": "create",
"tableName": "FetchCat Delivery Ledger"
},
"typeVersion": 1.1
},
{
"id": "50000000-0000-0000-4000-8000-000000000005",
"name": "Set Vinted Search Parameters",
"type": "n8n-nodes-base.set",
"position": [
-1328,
112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "vinted-search",
"name": "searchText",
"type": "string",
"value": "cycling jersey"
},
{
"id": "vinted-audience",
"name": "audience",
"type": "string",
"value": "Women"
},
{
"id": "vinted-domain",
"name": "domain",
"type": "string",
"value": "www.vinted.fr"
},
{
"id": "vinted-min-price",
"name": "minimumPrice",
"type": "number",
"value": 0
},
{
"id": "vinted-max-price",
"name": "maximumPrice",
"type": "number",
"value": 150
},
{
"id": "vinted-brands",
"name": "allowedBrands",
"type": "string",
"value": "MAAP"
},
{
"id": "vinted-sizes",
"name": "allowedSizes",
"type": "string",
"value": "S, XS"
},
{
"id": "vinted-colors",
"name": "allowedColors",
"type": "string",
"value": "blue, bleu, black, noir, white, blanc, multi, multicolor, multicolour, multicolore, red, rouge, yellow, jaune"
},
{
"id": "vinted-brand-ids",
"name": "brandIds",
"type": "string",
"value": ""
},
{
"id": "vinted-catalog-ids",
"name": "catalogIds",
"type": "string",
"value": ""
},
{
"id": "vinted-results",
"name": "maxResults",
"type": "number",
"value": 10
}
]
}
},
"typeVersion": 3.4
},
{
"id": "50000000-0000-0000-4000-8000-000000000006",
"name": "Validate Search Settings",
"type": "n8n-nodes-base.code",
"position": [
-848,
112
],
"parameters": {
"jsCode": "const input = $input.first()?.json || {};\nconst searchText = String(input.searchText || '').trim();\nconst domain = String(input.domain || '').trim().toLowerCase().replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\nconst minimumPrice = Number(input.minimumPrice ?? 0);\nconst maximumPrice = Number(input.maximumPrice ?? 0);\nconst maxResults = Number(input.maxResults ?? 10);\nconst normalize = (value) => String(value || '').normalize('NFKD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();\nconst parseList = (value) => String(value || '').split(',').map(normalize).filter(Boolean);\nconst parseIds = (value, label) => {\n const ids = String(value || '').split(',').map((entry) => entry.trim()).filter(Boolean).map(Number);\n if (ids.some((id) => !Number.isInteger(id) || id <= 0)) throw new Error(label + ' must contain comma-separated positive numeric Vinted IDs.');\n return [...new Set(ids)];\n};\nconst audienceMap = { any: 'Any', women: 'Women', men: 'Men', girls: 'Girls', boys: 'Boys' };\nconst audience = audienceMap[normalize(input.audience || 'Any')];\nconst allowedBrands = parseList(input.allowedBrands);\nconst allowedSizes = parseList(input.allowedSizes);\nconst allowedColors = parseList(input.allowedColors);\nconst brandIds = parseIds(input.brandIds, 'Brand IDs');\nconst catalogIds = parseIds(input.catalogIds, 'Catalog IDs');\nif (searchText.length < 2 || searchText.length > 200) throw new Error('Search text must be 2 to 200 characters.');\nif (!audience) throw new Error('Audience must be Any, Women, Men, Girls, or Boys.');\nif (!/^www\\.vinted\\.[a-z.]{2,10}$/.test(domain)) throw new Error('Use a public Vinted domain such as www.vinted.fr, www.vinted.de, or www.vinted.co.uk.');\nif (!Number.isFinite(minimumPrice) || minimumPrice < 0) throw new Error('Minimum price must be zero or greater.');\nif (!Number.isFinite(maximumPrice) || maximumPrice <= 0 || maximumPrice < minimumPrice) throw new Error('Maximum price must be greater than or equal to minimum price.');\nif (!Number.isInteger(maxResults) || maxResults < 1 || maxResults > 50) throw new Error('Maximum results must be an integer from 1 to 50.');\nconst audienceTerm = audience === 'Any' ? '' : audience.toLowerCase();\nconst searchTerms = new Set(normalize(searchText).split(' ').filter(Boolean));\nconst actorSearchText = audienceTerm && !searchTerms.has(audienceTerm) ? searchText + ' ' + audienceTerm : searchText;\nconst monitorKey = [domain, normalize(searchText), audience, minimumPrice, maximumPrice, [...allowedBrands].sort().join(','), [...allowedSizes].sort().join(','), [...allowedColors].sort().join(','), [...brandIds].sort((a, b) => a - b).join(','), [...catalogIds].sort((a, b) => a - b).join(',')].join('|');\nconst focusedBrands = brandIds.length ? [] : allowedBrands;\nconst searchCount = Math.max(1, focusedBrands.length);\nconst itemsPerSearch = Math.ceil(maxResults / searchCount);\nconst makeActorInput = (focusedBrand = '') => ({\n searchText: focusedBrand ? focusedBrand + ' ' + actorSearchText : actorSearchText,\n domain,\n priceMin: minimumPrice,\n priceMax: maximumPrice,\n maxItems: itemsPerSearch,\n order: 'newest_first',\n includeSeller: true,\n ...(brandIds.length ? { brandIds } : {}),\n ...(catalogIds.length ? { catalogIds } : {})\n});\nconst actorInputs = focusedBrands.length ? focusedBrands.map(makeActorInput) : [makeActorInput()];\nreturn [{ json: {\n searchText, actorSearchText, audience, domain, minimumPrice, maximumPrice, allowedBrands, allowedSizes, allowedColors, brandIds, catalogIds,\n maxResults, monitorKey, searchCount, itemsPerSearch, actorInputs\n} }];"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000032",
"name": "Build Brand Search Queries",
"type": "n8n-nodes-base.code",
"position": [
-608,
16
],
"parameters": {
"jsCode": "const config = $('Validate Search Settings').first().json;\nreturn config.actorInputs.map((actorInput, index) => ({ json: {\n actorInput,\n searchNumber: index + 1,\n searchCount: config.actorInputs.length\n} }));"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000008",
"name": "Start FetchCat Vinted Search",
"type": "n8n-nodes-base.httpRequest",
"position": [
-160,
16
],
"parameters": {
"url": "https://api.apify.com/v2/acts/F1GAwbqJ9xc9h7P87/runs",
"method": "POST",
"options": {
"timeout": 310000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={{ $json.actorInput }}",
"sendBody": true,
"sendQuery": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "waitForFinish",
"value": "300"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Accept-Encoding",
"value": "identity"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "50000000-0000-0000-4000-8000-000000000009",
"name": "Download Vinted Search Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
80,
16
],
"parameters": {
"url": "=https://api.apify.com/v2/datasets/{{ $json.data.defaultDatasetId }}/items",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "clean",
"value": "true"
},
{
"name": "limit",
"value": "={{ $(\"Validate Search Settings\").first().json.maxResults }}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "50000000-0000-0000-4000-8000-000000000010",
"name": "Normalize and Filter Listings",
"type": "n8n-nodes-base.code",
"position": [
304,
208
],
"parameters": {
"jsCode": "const config = $('Validate Search Settings').first().json;\nconst rawListings = $input.all().flatMap((item) => {\n let payload = item.json?.data ?? item.json;\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { throw new Error('Apify returned invalid JSON.'); }\n }\n return Array.isArray(payload) ? payload : [payload];\n}).slice(0, config.maxResults);\nconst normalized = [];\nconst seenListingIds = new Set();\nconst filterProgress = { validListings: 0, withinPrice: 0, matchingBrand: 0, matchingSize: 0 };\nconst normalize = (value) => String(value || '').normalize('NFKD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();\nconst containsPhrase = (text, phrase) => (' ' + text + ' ').includes(' ' + phrase + ' ');\nfor (const listing of rawListings) {\n const id = String(listing.id || '').trim();\n const title = String(listing.title || '').trim();\n const url = String(listing.url || '').trim();\n const priceAmount = Number(listing.priceAmount);\n const brand = String(listing.brandTitle || '').trim();\n const size = String(listing.sizeTitle || '').trim();\n const normalizedTitle = normalize(title);\n const normalizedBrand = normalize(brand);\n const normalizedSize = normalize(size);\n const sizeParts = new Set([normalizedSize, ...String(size || '').split(/[\\/,;|()[\\]]+/).map(normalize).filter(Boolean)]);\n const matchedColors = config.allowedColors.filter((color) => containsPhrase(normalizedTitle, color));\n if (!id || !title || !/^https:\\/\\//.test(url) || !Number.isFinite(priceAmount)) continue;\n if (seenListingIds.has(id)) continue;\n seenListingIds.add(id);\n filterProgress.validListings += 1;\n if (priceAmount < config.minimumPrice || priceAmount > config.maximumPrice) continue;\n filterProgress.withinPrice += 1;\n if (config.allowedBrands.length && !config.allowedBrands.includes(normalizedBrand)) continue;\n filterProgress.matchingBrand += 1;\n if (config.allowedSizes.length && !config.allowedSizes.some((allowedSize) => sizeParts.has(allowedSize))) continue;\n filterProgress.matchingSize += 1;\n normalized.push({ json: {\n listingId: id,\n itemKey: config.monitorKey + '|' + id,\n monitorKey: config.monitorKey,\n title,\n priceAmount,\n currency: String(listing.currency || ''),\n brand: brand || 'Brand not specified',\n size: size || 'Size not specified',\n matchedColors,\n audience: config.audience,\n searchText: config.searchText,\n condition: String(listing.status || 'Condition not specified'),\n seller: String(listing.sellerLogin || 'Seller not specified'),\n favoriteCount: Number.isFinite(Number(listing.favoriteCount)) ? Number(listing.favoriteCount) : null,\n viewCount: Number.isFinite(Number(listing.viewCount)) && Number(listing.viewCount) > 0 ? Number(listing.viewCount) : null,\n photoUrl: Array.isArray(listing.photoUrls) && listing.photoUrls[0] ? String(listing.photoUrls[0]) : '',\n url,\n scrapedAt: String(listing.scrapedAt || '')\n } });\n}\nif (normalized.length) return normalized;\nconst stages = [\n ['valid listing data', filterProgress.validListings],\n ['price', filterProgress.withinPrice],\n ['brand', filterProgress.matchingBrand],\n ['size', filterProgress.matchingSize]\n];\nconst blockedAt = rawListings.length === 0 ? 'search results' : (stages.find(([, count]) => count === 0)?.[0] || 'configured filters');\nreturn [{ json: {\n noMatches: true,\n status: 'No listings matched all configured filters.',\n returnedCount: rawListings.length,\n blockedAt,\n filterProgress,\n returnedBrands: [...new Set(rawListings.map((listing) => String(listing.brandTitle || 'Brand not specified').trim()))].sort(),\n returnedSizes: [...new Set(rawListings.map((listing) => String(listing.sizeTitle || 'Size not specified').trim()))].sort(),\n suggestion: rawListings.length === 0\n ? 'Broaden searchText or confirm the selected Vinted domain.'\n : 'Clear or broaden the blocking filter, use exact Vinted brand/catalog IDs when available, or increase maxResults for wider coverage.'\n} }];"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000029",
"name": "Check for Matching Listings",
"type": "n8n-nodes-base.if",
"position": [
544,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "vinted-matches-condition",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.noMatches !== true }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "50000000-0000-0000-4000-8000-000000000011",
"name": "Keep Undelivered Listings",
"type": "n8n-nodes-base.dataTable",
"position": [
784,
16
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "workflowSlug",
"keyValue": "vinted-new-listing-alerts"
},
{
"keyName": "itemKey",
"keyValue": "={{ $json.itemKey }}"
}
]
},
"matchType": "allConditions",
"operation": "rowNotExists",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "FetchCat Delivery Ledger"
}
},
"typeVersion": 1.1
},
{
"id": "50000000-0000-0000-4000-8000-000000000030",
"name": "Show No-Match Details",
"type": "n8n-nodes-base.code",
"position": [
784,
208
],
"parameters": {
"jsCode": "return $input.all();"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000012",
"name": "Build Telegram Alert Batch",
"type": "n8n-nodes-base.code",
"position": [
1008,
16
],
"parameters": {
"jsCode": "const listings = $input.all().map((item) => item.json);\nif (listings.length === 0) return [];\nreturn [{ json: { listings } }];"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000017",
"name": "Format Telegram Messages",
"type": "n8n-nodes-base.code",
"position": [
1248,
368
],
"parameters": {
"jsCode": "const escapeHtml = (value) => String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst all = $json.listings;\nconst config = $('Validate Search Settings').first().json;\nconst messages = [];\nfor (let offset = 0; offset < all.length; offset += 5) {\n const chunk = all.slice(offset, offset + 5);\n const lines = chunk.map((listing, index) => {\n const position = offset + index + 1;\n const price = listing.priceAmount.toLocaleString('en-US', { maximumFractionDigits: 2 }) + (listing.currency ? ' ' + listing.currency : '');\n const engagement = [listing.viewCount === null ? null : listing.viewCount + ' views', listing.favoriteCount === null ? null : listing.favoriteCount + ' favorites'].filter(Boolean).join(' | ');\n return position + '. <b>' + escapeHtml(listing.title.slice(0, 110)) + '</b>\\n' +\n '<b>Price:</b> ' + escapeHtml(price) + '\\n' +\n '<b>Brand:</b> ' + escapeHtml(listing.brand) + ' | <b>Size:</b> ' + escapeHtml(listing.size) +\n (listing.matchedColors.length ? ' | <b>Color:</b> ' + escapeHtml(listing.matchedColors.join(', ')) : '') + '\\n' +\n '<b>Condition:</b> ' + escapeHtml(listing.condition) + ' | <b>Seller:</b> ' + escapeHtml(listing.seller) +\n (engagement ? '\\n' + escapeHtml(engagement) : '') + '\\n' +\n '<a href=\"' + escapeHtml(listing.url) + '\">Open Vinted listing</a>';\n });\n const heading = offset === 0\n ? '<b>' + all.length + ' new Vinted ' + (all.length === 1 ? 'match' : 'matches') + '</b>'\n : '<b>Vinted matches continued</b>';\n const criteria = offset === 0 ? '\\n<b>Search:</b> ' + escapeHtml(config.searchText) + ' | <b>Audience:</b> ' + escapeHtml(config.audience) : '';\n messages.push({ json: { telegramMessage: heading + criteria + '\\n\\n' + lines.join('\\n\\n') } });\n}\nreturn messages;"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000018",
"name": "Send New Listings to Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
1488,
368
],
"parameters": {
"text": "={{ $json.telegramMessage }}",
"chatId": "123456789",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false,
"disable_notification": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "50000000-0000-0000-4000-8000-000000000019",
"name": "Prepare Delivery Records",
"type": "n8n-nodes-base.code",
"position": [
1712,
416
],
"parameters": {
"jsCode": "return $('Build Telegram Alert Batch').first().json.listings.map((listing) => ({ json: { workflowSlug: 'vinted-new-listing-alerts', itemKey: listing.itemKey } }));"
},
"typeVersion": 2
},
{
"id": "50000000-0000-0000-4000-8000-000000000020",
"name": "Record Delivered Listings",
"type": "n8n-nodes-base.dataTable",
"position": [
2000,
416
],
"parameters": {
"columns": {
"value": {
"itemKey": "={{ $json.itemKey }}",
"deliveredAt": "={{ $now.toISO() }}",
"destination": "Telegram",
"workflowSlug": "={{ $json.workflowSlug }}"
},
"schema": [
{
"id": "workflowSlug",
"type": "string",
"display": true,
"required": false,
"displayName": "workflowSlug",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "itemKey",
"type": "string",
"display": true,
"required": false,
"displayName": "itemKey",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "destination",
"type": "string",
"display": true,
"required": false,
"displayName": "destination",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "deliveredAt",
"type": "dateTime",
"display": true,
"required": false,
"displayName": "deliveredAt",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "FetchCat Delivery Ledger"
}
},
"typeVersion": 1.1
},
{
"id": "50000000-0000-0000-4000-8000-000000000021",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2448,
-256
],
"parameters": {
"width": 480,
"height": 960,
"content": "## Vinted New-Listing Alerts to Telegram\n\nMonitor a focused public Vinted search and receive Telegram alerts for matching listings that have not been delivered before. This workflow runs `fetch_cat/vinted-search-scraper` through Apify, works on n8n Cloud or self-hosted n8n, and does not require OpenAI.\n\n### How it works\n\n1. Starts manually or on the editable hourly schedule.\n2. Validates the marketplace, query, audience, price, brand, size, color labels, and result limit.\n3. Runs focused FetchCat searches and downloads the newest Vinted listings.\n4. Filters results and removes listing IDs already present in the delivery ledger.\n5. Sends readable Telegram alerts and records IDs only after delivery succeeds.\n\n### Setup\n\n- [ ] Edit `Set Vinted Search Parameters` for your marketplace and saved-search criteria.\n- [ ] Connect one Apify HTTP Header Auth credential to both FetchCat HTTP Request nodes.\n- [ ] Connect a Telegram Bot credential and choose the destination chat.\n- [ ] Adjust `Hourly Alert Trigger` when a different interval is worth the extra executions.\n- [ ] Run manually, confirm the current matches arrive, then publish the workflow."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000022",
"name": "Trigger Workflow",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1888,
-256
],
"parameters": {
"color": 7,
"height": 624,
"content": "## Trigger the monitor\n\nStarts manually for testing or on the editable hourly schedule."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000023",
"name": "Initialize and Configure Search",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
-208
],
"parameters": {
"color": 7,
"width": 1152,
"height": 480,
"content": "## Initialize and configure search\n\nCreates the delivery ledger, reads the saved-search parameters, validates every value, and builds one focused Actor input per brand name. A size such as `M`, `38`, or `10` matches Vinted's combined value `M / 38 / 10`."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000024",
"name": "Execute Search",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-240
],
"parameters": {
"color": 7,
"width": 432,
"height": 416,
"content": "## Execute the FetchCat search\n\nStarts `fetch_cat/vinted-search-scraper` through Apify and downloads the completed dataset."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000025",
"name": "Process and Filter Results",
"type": "n8n-nodes-base.stickyNote",
"position": [
256,
-240
],
"parameters": {
"color": 7,
"width": 672,
"height": 608,
"content": "## Process and filter results\n\nNormalizes listings, applies price, brand, and size filters, explains empty results, and keeps only IDs absent from the delivery ledger."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000031",
"name": "Prepare and Send Alerts",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
-208
],
"parameters": {
"color": 7,
"width": 672,
"height": 752,
"content": "## Prepare and send alerts\n\nBatches unseen listings, formats readable Telegram messages, and sends current matches immediately, including on the first run."
},
"typeVersion": 1
},
{
"id": "50000000-0000-0000-4000-8000-000000000027",
"name": "Log and Finalize",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
160
],
"parameters": {
"color": 7,
"width": 480,
"height": 416,
"content": "## Record successful delivery\n\nWrites listing IDs only after Telegram succeeds so interrupted deliveries remain retryable."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"timezone": "Europe/Lisbon",
"binaryMode": "separate",
"callerPolicy": "workflowsFromSameOwner",
"executionOrder": "v1",
"saveManualExecutions": true
},
"versionId": "b6dbfc9a-b16d-44cb-a519-d5f453d25cd2",
"nodeGroups": [],
"connections": {
"Hourly Alert Trigger": {
"main": [
[
{
"node": "Create Delivery Ledger",
"type": "main",
"index": 0
}
]
]
},
"Manual Start Trigger": {
"main": [
[
{
"node": "Create Delivery Ledger",
"type": "main",
"index": 0
}
]
]
},
"Create Delivery Ledger": {
"main": [
[
{
"node": "Set Vinted Search Parameters",
"type": "main",
"index": 0
}
]
]
},
"Format Telegram Messages": {
"main": [
[
{
"node": "Send New Listings to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Prepare Delivery Records": {
"main": [
[
{
"node": "Record Delivered Listings",
"type": "main",
"index": 0
}
]
]
},
"Validate Search Settings": {
"main": [
[
{
"node": "Build Brand Search Queries",
"type": "main",
"index": 0
}
]
]
},
"Keep Undelivered Listings": {
"main": [
[
{
"node": "Build Telegram Alert Batch",
"type": "main",
"index": 0
}
]
]
},
"Build Brand Search Queries": {
"main": [
[
{
"node": "Start FetchCat Vinted Search",
"type": "main",
"index": 0
}
]
]
},
"Build Telegram Alert Batch": {
"main": [
[
{
"node": "Format Telegram Messages",
"type": "main",
"index": 0
}
]
]
},
"Check for Matching Listings": {
"main": [
[
{
"node": "Keep Undelivered Listings",
"type": "main",
"index": 0
}
],
[
{
"node": "Show No-Match Details",
"type": "main",
"index": 0
}
]
]
},
"Set Vinted Search Parameters": {
"main": [
[
{
"node": "Validate Search Settings",
"type": "main",
"index": 0
}
]
]
},
"Start FetchCat Vinted Search": {
"main": [
[
{
"node": "Download Vinted Search Results",
"type": "main",
"index": 0
}
]
]
},
"Normalize and Filter Listings": {
"main": [
[
{
"node": "Check for Matching Listings",
"type": "main",
"index": 0
}
]
]
},
"Send New Listings to Telegram": {
"main": [
[
{
"node": "Prepare Delivery Records",
"type": "main",
"index": 0
}
]
]
},
"Download Vinted Search Results": {
"main": [
[
{
"node": "Normalize and Filter Listings",
"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.
httpHeaderAuthtelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs on an hourly schedule (or manually) to scrape the newest Vinted listings via the Apify FetchCat actor, filters matches by your saved search criteria, deduplicates previously sent results using an n8n Data Table ledger, and sends new-listing alerts to a…
Source: https://n8n.io/workflows/17289/ — 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 n8n template automates Amazon product scraping using the Olostep API. Simply enter a search query, and the workflow scrapes multiple Amazon search pages to extract product titles and URLs. Result
Support-Scraper. Uses httpRequest, dataTable, googleDrive. Event-driven trigger; 18 nodes.
This n8n template automates Zillow property data collection by scraping Zillow search results using the Olostep API. It extracts property price, link to listing, and location, removes duplicates, and
Google Maps Lead Gen - Apify to GSheet. Uses httpRequest, googleSheets, telegram. Event-driven trigger; 8 nodes.
Telegram Lead Gen - Apify Google Maps to Sheets. Uses telegramTrigger, telegram, httpRequest, openAi. Event-driven trigger; 25 nodes.