This workflow follows the HTTP Request → Telegram 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": "CliquezDanserz_Google_Ads_Optimization",
"nodes": [
{
"parameters": {
"triggerTimes": {
"item": [
{
"hour": 6,
"minute": 0
}
]
}
},
"name": "Schedule Daily",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
200,
-200
],
"id": "b87f9be8-4af2-462f-95b6-35a0b04eb9d0"
},
{
"parameters": {
"values": {
"string": [
{
"name": "customer_id",
"value": "1234567890"
}
],
"number": [
{
"name": "client_value",
"value": 50
},
{
"name": "roi_low_threshold",
"value": 1
},
{
"name": "roi_high_threshold",
"value": 3
},
{
"name": "cost_conv_high",
"value": 20
},
{
"name": "cost_conv_low",
"value": 10
},
{
"name": "spent_no_conv_limit",
"value": 10
}
]
},
"options": {}
},
"name": "Set Constants",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [
400,
-200
],
"id": "9bd11c66-3460-4b50-b8fa-0685588b3b0f"
},
{
"parameters": {
"authentication": "predefinedCredentialType",
"requestMethod": "POST",
"url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/googleAds:search",
"options": {},
"bodyParametersJson": "={\n \"query\": \"SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.cost_per_conversion FROM ad_group WHERE segments.date DURING YESTERDAY\"\n}"
},
"name": "Get Ad Group Stats",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
600,
-200
],
"id": "32d32a8f-9c2b-4b8e-a0ba-87d521c5c537",
"credentials": {
"googleAdsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"functionCode": "const raw = $json.results || [];\nconst clientValue = $json[\"client_value\"] || 50;\n\nconst output = [];\nfor (const row of raw) {\n const campaignId = row.campaign?.id || row[\"campaign.id\"];\n const campaignName = row.campaign?.name || row[\"campaign.name\"];\n const adGroupId = row.adGroup?.id || row[\"ad_group.id\"];\n const adGroupName = row.adGroup?.name || row[\"ad_group.name\"];\n const impressions = Number(row.metrics?.impressions || row[\"metrics.impressions\"] || 0);\n const clicks = Number(row.metrics?.clicks || row[\"metrics.clicks\"] || 0);\n const ctr = Number(row.metrics?.ctr || row[\"metrics.ctr\"] || 0) * 100;\n const averageCpcMicros = Number(row.metrics?.averageCpc || row[\"metrics.average_cpc\"] || 0);\n const costMicros = Number(row.metrics?.costMicros || row[\"metrics.cost_micros\"] || 0);\n const conversions = Number(row.metrics?.conversions || row[\"metrics.conversions\"] || 0);\n const costPerConvMicros = Number(row.metrics?.costPerConversion || row[\"metrics.cost_per_conversion\"] || 0);\n\n const costEur = costMicros / 1e6;\n const cpcEur = averageCpcMicros / 1e6;\n const costPerConvEur = costPerConvMicros > 0 ? costPerConvMicros / 1e6 : 0;\n\n let roi = 0;\n if (costEur > 0) {\n roi = ((conversions * clientValue) - costEur) / costEur;\n }\n\n output.push({\n campaign_id: campaignId,\n campaign_name: campaignName,\n ad_group_id: adGroupId,\n ad_group_name: adGroupName,\n impressions,\n clicks,\n ctr: parseFloat(ctr.toFixed(2)),\n cost_eur: parseFloat(costEur.toFixed(2)),\n cpc_eur: parseFloat(cpcEur.toFixed(2)),\n conversions,\n cost_per_conv_eur: parseFloat(costPerConvEur.toFixed(2)),\n roi: parseFloat(roi.toFixed(2))\n });\n}\nreturn output;\n"
},
"name": "Process Ad Group Stats",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
800,
-200
],
"id": "7d1bf40b-d691-4a05-96e9-a4fa08e3d5d9"
},
{
"parameters": {
"functionCode": "// Determine needed adjustments\nconst items = $input.all();\nconst roiLow = $json[\"roi_low_threshold\"] || 1;\nconst roiHigh = $json[\"roi_high_threshold\"] || 3;\nconst costConvHigh = $json[\"cost_conv_high\"] || 20;\nconst costConvLow = $json[\"cost_conv_low\"] || 10;\n\nlet output = [];\nfor (const item of items) {\n const j = item.json;\n let action = \"none\";\n\n if (j.roi < roiLow || j.cost_per_conv_eur > costConvHigh) {\n action = \"decrease\";\n } else if (j.roi > roiHigh || (j.cost_per_conv_eur > 0 && j.cost_per_conv_eur < costConvLow)) {\n action = \"increase\";\n }\n\n output.push({\n json: {\n ...j,\n action\n }\n });\n}\nreturn output;\n"
},
"name": "Decide Bids",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1000,
-200
],
"id": "b3052bcb-68bf-4986-a21e-fcfb4359b095"
},
{
"parameters": {
"propertyName": "action",
"switch": [
{
"value": "decrease"
},
{
"value": "increase"
}
]
},
"name": "Split Increase/Decrease",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"position": [
1200,
-200
],
"id": "f4be5c66-9709-4d1f-918b-cb82db217c99"
},
{
"parameters": {
"authentication": "predefinedCredentialType",
"requestMethod": "POST",
"url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/adGroups:mutate",
"options": {},
"bodyParametersJson": "={\n \"operations\": [\n {\n \"update\": {\n \"resourceName\": \"customers/{{$json[\"customer_id\"]}}/adGroups/{{$json[\"ad_group_id\"]}}\",\n \"cpcBidMicros\": {{ Math.floor($json[\"cpc_eur\"] * 1000000 * 0.9) }}\n },\n \"updateMask\": \"cpcBidMicros\"\n }\n ]\n}"
},
"name": "Apply Decrease Bid",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1400,
-280
],
"id": "585b6466-a073-447c-b6b2-8aa2bd398d02",
"credentials": {
"googleAdsOAuth2Api": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"authentication": "predefinedCredentialType",
"requestMethod": "POST",
"url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/adGroups:mutate",
"options": {},
"bodyParametersJson": "={\n \"operations\": [\n {\n \"update\": {\n \"resourceName\": \"customers/{{$json[\"customer_id\"]}}/adGroups/{{$json[\"ad_group_id\"]}}\",\n \"cpcBidMicros\": {{ Math.floor($json[\"cpc_eur\"] * 1000000 * 1.15) }}\n },\n \"updateMask\": \"cpcBidMicros\"\n }\n ]\n}"
},
"name": "Apply Increase Bid",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1400,
-120
],
"id": "57cb1f3d-7c84-4c5e-b3ac-9719a4b5c20c",
"credentials": {
"googleAdsOAuth2Api": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"resource": "task",
"operation": "create",
"workspace": "",
"name": "={{ \"[Google Ads] \" + ($json[\"action\"] === \"increase\" ? \"Augmentation\" : \"Diminution\") + \" ench\u00e8re\" }}",
"additionalFields": {
"notes": "=Date: {{ new Date().toISOString().split('T')[0] }}\nCampagne: {{ $json[\"campaign_name\"] }}\nGroupe d'annonces: {{ $json[\"ad_group_name\"] }}\nAction: {{ $json[\"action\"] === \"increase\" ? \"+15%\" : \"-10%\" }}\nROI: {{ $json[\"roi\"] }}",
"projects": [
"Cliquezdansez"
]
}
},
"name": "Log Bid Change to Asana",
"type": "n8n-nodes-base.asana",
"typeVersion": 1,
"position": [
1600,
-200
],
"id": "584bcf07-0577-4d70-b6f2-efda5ffbc8af",
"credentials": {
"asanaApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"functionCode": "const items = $input.all();\nreturn items.filter(item => item.json.action !== 'none');"
},
"name": "Filter No-Action",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1100,
-200
],
"id": "edda2dbb-6b31-43f5-bc42-132f49ce30c8"
},
{
"parameters": {
"authentication": "predefinedCredentialType",
"requestMethod": "POST",
"url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/googleAds:search",
"options": {},
"bodyParametersJson": "={\n \"query\": \"SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, ad_group_criterion.keyword.text, metrics.cost_micros, metrics.conversions FROM ad_group_criterion WHERE segments.date DURING LAST_30_DAYS AND metrics.conversions = 0 AND metrics.cost_micros > {{ $json[\"spent_no_conv_limit\"] * 1000000 }} AND ad_group_criterion.type = 'KEYWORD'\"\n}"
},
"name": "Find Inefficient Keywords",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
400,
100
],
"id": "bd6caaf8-4525-4423-86ca-0b9c555036f6",
"credentials": {
"googleAdsOAuth2Api": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"functionCode": "const raw = $json.results || [];\nlet output = [];\nfor (const row of raw) {\n const campaignId = row.campaign?.id || row[\"campaign.id\"];\n const campaignName = row.campaign?.name || row[\"campaign.name\"];\n const adGroupId = row.adGroup?.id || row[\"ad_group.id\"];\n const adGroupName = row.adGroup?.name || row[\"ad_group.name\"];\n const keywordText = row.adGroupCriterion?.keyword?.text || row[\"ad_group_criterion.keyword.text\"];\n output.push({ json: {\n campaign_id: campaignId,\n campaign_name: campaignName,\n ad_group_id: adGroupId,\n ad_group_name: adGroupName,\n keyword: keywordText\n }});\n}\nreturn output;\n"
},
"name": "Process Keywords",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
600,
100
],
"id": "65693d57-60e7-4c4d-8d43-574866ecf0f2"
},
{
"parameters": {
"authentication": "predefinedCredentialType",
"requestMethod": "POST",
"url": "=https://googleads.googleapis.com/v14/customers/{{$json[\"customer_id\"]}}/campaignCriteria:mutate",
"options": {},
"bodyParametersJson": "={\n \"operations\": [\n {\n \"create\": {\n \"campaign\": \"customers/{{$json[\"customer_id\"]}}/campaigns/{{$json[\"campaign_id\"]}}\",\n \"negative\": true,\n \"keyword\": {\n \"text\": \"{{$json[\"keyword\"]}}\",\n \"matchType\": \"BROAD\"\n }\n }\n }\n ]\n}"
},
"name": "Add Negative Keyword",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
800,
100
],
"id": "8aa49ed4-3c56-4d97-84c4-643c4e2893cd",
"credentials": {
"googleAdsOAuth2Api": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"resource": "task",
"operation": "create",
"workspace": "",
"name": "={{ \"[Google Ads] Mot-cl\u00e9 n\u00e9gatif ajout\u00e9: \" + $json[\"keyword\"] }}",
"additionalFields": {
"notes": "=Date: {{ new Date().toISOString().split('T')[0] }}\nCampagne: {{ $json[\"campaign_name\"] }}\nMot-cl\u00e9: {{ $json[\"keyword\"] }}\n\nCar trop de d\u00e9penses sans conversions.",
"projects": [
"Cliquezdansez"
]
}
},
"name": "Log Negative Keyword to Asana",
"type": "n8n-nodes-base.asana",
"typeVersion": 1,
"position": [
1000,
100
],
"id": "6f7afec8-a4b7-4748-8ce3-7a1e3bc31949",
"credentials": {
"asanaApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"functionCode": "const items = $input.all();\nlet totalConv = 0;\nlet totalCost = 0;\nfor (const it of items) {\n const conv = it.json.conversions || 0;\n const cost = it.json.cost_eur || 0;\n totalConv += conv;\n totalCost += cost;\n}\nlet roiGlobal = 0;\nif (totalCost > 0) {\n roiGlobal = ((totalConv * 50) - totalCost) / totalCost;\n}\n// Simple alert if ROI < 0.5\nif (roiGlobal < 0.5 && totalCost > 0) {\n return [{ json: { message: `Alerte: ROI global = ${roiGlobal.toFixed(2)} (< 0.5)` } }];\n}\nreturn [];\n"
},
"name": "Check Global ROI",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1000,
-80
],
"id": "3fa46b59-c49a-4ba2-a1b7-a13cece8bd92"
},
{
"parameters": {
"chatId": "",
"text": "={{ $json[\"message\"] }}",
"additionalFields": {
"disable_notification": false
}
},
"name": "Send Telegram Alert",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1200,
-80
],
"id": "a852281e-7c44-4a84-a71c-a2fa6a8a3639",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
}
],
"connections": {
"Schedule Daily": {
"main": [
[
{
"node": "Set Constants",
"type": "main",
"index": 0
},
{
"node": "Find Inefficient Keywords",
"type": "main",
"index": 0
}
]
]
},
"Set Constants": {
"main": [
[
{
"node": "Get Ad Group Stats",
"type": "main",
"index": 0
}
]
]
},
"Get Ad Group Stats": {
"main": [
[
{
"node": "Process Ad Group Stats",
"type": "main",
"index": 0
}
]
]
},
"Process Ad Group Stats": {
"main": [
[
{
"node": "Check Global ROI",
"type": "main",
"index": 0
},
{
"node": "Decide Bids",
"type": "main",
"index": 0
}
]
]
},
"Decide Bids": {
"main": [
[
{
"node": "Filter No-Action",
"type": "main",
"index": 0
}
]
]
},
"Filter No-Action": {
"main": [
[
{
"node": "Split Increase/Decrease",
"type": "main",
"index": 0
}
]
]
},
"Split Increase/Decrease": {
"main": [
[
{
"node": "Apply Decrease Bid",
"type": "main",
"index": 0
},
{
"node": "Log Bid Change to Asana",
"type": "main",
"index": 0
}
],
[
{
"node": "Apply Increase Bid",
"type": "main",
"index": 0
},
{
"node": "Log Bid Change to Asana",
"type": "main",
"index": 1
}
]
]
},
"Apply Decrease Bid": {
"main": [
[]
]
},
"Apply Increase Bid": {
"main": [
[]
]
},
"Log Bid Change to Asana": {
"main": [
[]
]
},
"Find Inefficient Keywords": {
"main": [
[
{
"node": "Process Keywords",
"type": "main",
"index": 0
}
]
]
},
"Process Keywords": {
"main": [
[
{
"node": "Add Negative Keyword",
"type": "main",
"index": 0
},
{
"node": "Log Negative Keyword to Asana",
"type": "main",
"index": 0
}
]
]
},
"Add Negative Keyword": {
"main": [
[]
]
},
"Log Negative Keyword to Asana": {
"main": [
[]
]
},
"Check Global ROI": {
"main": [
[
{
"node": "Send Telegram Alert",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"id": "workflow_google_ads_final",
"tags": []
}
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.
asanaApigoogleAdsOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
CliquezDanserz_Google_Ads_Optimization. Uses httpRequest, asana, telegram. Scheduled trigger; 16 nodes.
Source: https://gist.github.com/Igor1402-star/9a15667c7e664b2d26ee108565beca2f — 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.
Auto Hunt. Uses httpRequest, googleSheets, rssFeedRead, telegram. Scheduled trigger; 78 nodes.
Auto-Hunt. Uses httpRequest, googleSheets, rssFeedRead, telegram. Scheduled trigger; 78 nodes.
This workflow runs daily at 9 AM, uses Perplexity to compile vaping industry news, optionally captures article screenshots via Browserless, and generates a HeyGen avatar video from a template. It then
. Uses googleSheets, telegram, httpRequest, wise. Scheduled trigger; 36 nodes.
GNCA AI News Pipeline. Uses rssFeedRead, httpRequest, telegram, errorTrigger. Scheduled trigger; 31 nodes.