AutomationFlowsWeb Scraping › Workflow for Submitting Changed Sitemap Urls Using Google Indexing API and…

Workflow for Submitting Changed Sitemap Urls Using Google Indexing API and…

Original n8n title: Workflow for Submitting Changed Sitemap Urls Using Google Indexing API and Bing Indexnow

ByGeoffroy @jojoq42 on n8n.io

Site owners, SEOs, and marketers who want a single automation to notify Google (Indexing API) and Bing (via IndexNow) whenever site URLs are added or updated. No more need to update it manually. Hours saved

Event trigger★★★★☆ complexity28 nodesHTTP RequestXML
Web Scraping Trigger: Event Nodes: 28 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #8778 — we link there as the canonical source.

This workflow follows the HTTP Request → XML 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 →

Download .json
{
  "id": "Mbf12LrYQKTF8pv3",
  "name": "Public SEO - Google & Bing Index Automation",
  "tags": [],
  "nodes": [
    {
      "id": "48f2a08a-df7a-4401-99bf-561b9b824706",
      "name": "When clicking \"Test workflow\"",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -6112,
        208
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ff55b42e-cf6f-4508-97df-268575e8643c",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -6112,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "daysInterval": 7,
              "triggerAtHour": 2,
              "triggerAtMinute": 20
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "3d20ee43-72d9-4504-9ba9-a0eb201b10b2",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -5792,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "62b17339-0608-4e32-bdc7-f2842e80c830",
              "name": "SITE_URL",
              "type": "string",
              "value": ""
            },
            {
              "id": "f0e1185a-37b2-424d-9ee4-3c5c7cb4dfdf",
              "name": "SITEMAP_URL",
              "type": "string",
              "value": ""
            },
            {
              "id": "4d32ab5c-66c2-4eb6-8f31-2eef77495c35",
              "name": "DAYS_BACK",
              "type": "number",
              "value": 7
            },
            {
              "id": "e9f9eccc-eaa4-4fbd-af28-12e33c26b01e",
              "name": "BATCH_SIZE",
              "type": "number",
              "value": 500
            },
            {
              "id": "b3ac1b81-77f0-4081-892f-d92f7029a11b",
              "name": "USE_GOOGLE",
              "type": "boolean",
              "value": true
            },
            {
              "id": "b435a0dc-3ec5-4161-a9e0-416869d8c6e5",
              "name": "USE_INDEXNOW",
              "type": "boolean",
              "value": true
            },
            {
              "id": "2481f0c9-dc4f-44dc-97c7-b597bede50f6",
              "name": "INDEXNOW_KEY",
              "type": "string",
              "value": ""
            },
            {
              "id": "d48a7a0f-a2f9-41a9-904e-e5b5686c0e2a",
              "name": "INDEXNOW_KEY_URL",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "7a830e64-deec-4510-896a-dca0061ec92e",
      "name": "Get sitemap.xml",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -5504,
        112
      ],
      "parameters": {
        "url": "={{ $json.SITEMAP_URL }}",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "5758fc47-d125-40ae-a114-2419e0625236",
      "name": "Convert sitemap to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        -5280,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b549d6c6-978d-4d21-b549-f0975eab16df",
      "name": "Get content-specific sitemaps",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -5056,
        112
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "sitemapindex.sitemap"
      },
      "typeVersion": 1
    },
    {
      "id": "b83c4ac7-cda3-4487-b0fa-344c2250ee1e",
      "name": "Get content of each sitemap",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -4832,
        112
      ],
      "parameters": {
        "url": "={{ $json.loc }}",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "841edc35-aa92-48a6-b6b4-ff0dbf67efd7",
      "name": "convert page data to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        -4608,
        112
      ],
      "parameters": {
        "options": {
          "explicitArray": false
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d66b327c-f641-4d99-9d5b-7b29b20f8d90",
      "name": "Force urlset.url to array",
      "type": "n8n-nodes-base.set",
      "position": [
        -4384,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "urlset.url",
              "type": "array",
              "value": "={{ $json.urlset && $json.urlset.url ? ($json.urlset.url[0] ? $json.urlset.url : [$json.urlset.url]) : [] }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "6b1490a3-f874-4259-887e-6fad9bfc82c9",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -4160,
        112
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "urlset.url"
      },
      "typeVersion": 1
    },
    {
      "id": "4417b088-ab0f-46ce-9b9f-00d31d4748c7",
      "name": "Sort",
      "type": "n8n-nodes-base.sort",
      "position": [
        -3936,
        112
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "lastmod"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b8363b24-a59c-489f-a940-37377d271cfc",
      "name": "Assign mandatory sitemap fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -3712,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "lastmod",
              "type": "string",
              "value": "={{ $json.lastmod }}"
            },
            {
              "name": "loc",
              "type": "string",
              "value": "={{ $json.loc }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "c3f46292-66c4-4cf5-a526-29c6d0881576",
      "name": "Filter: lastmod within DAYS_BACK",
      "type": "n8n-nodes-base.code",
      "position": [
        -3488,
        112
      ],
      "parameters": {
        "jsCode": "const daysBack = $items('Config')[0].json.DAYS_BACK || 7;\nconst cutoff = new Date(Date.now() - daysBack*24*60*60*1000);\nconst items = $items().filter(i => {\n  const lm = i.json.lastmod ? new Date(i.json.lastmod) : null;\n  return lm && lm >= cutoff;}).map(i => ({ json: { loc: i.json.loc, lastmod: i.json.lastmod } }));\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "1030c52f-fe20-4132-b189-96cd37d0245a",
      "name": "Loop Over Items (Google)",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -2992,
        -144
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "f86127b7-02df-42a0-8300-94dbee7d3bc6",
      "name": "Check status (Google)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -2768,
        -128
      ],
      "parameters": {
        "url": "=https://indexing.googleapis.com/v3/urlNotifications/metadata?url={{ encodeURIComponent($json.loc) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "b8e4756d-20fe-4649-b2b2-34d48c989a7a",
      "name": "is new? (Google)",
      "type": "n8n-nodes-base.if",
      "position": [
        -2576,
        -224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3ab7c657-442a-42cd-8ae8-e540da5d9eb8",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              },
              "leftValue": "={{ $('Loop Over Items (Google)').item.json.lastmod }}",
              "rightValue": "={{ $json.body.latestUpdate.notifyTime }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "92ed404d-d2c9-4a7f-83ff-487102dbc92a",
      "name": "URL Updated (Google)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2352,
        -112
      ],
      "parameters": {
        "url": "=https://indexing.googleapis.com/v3/urlNotifications:publish",
        "method": "POST",
        "options": {},
        "jsonBody": "={ \"url\": \"{{ $('Loop Over Items (Google)').item.json.loc }}\", \"type\": \"URL_UPDATED\" }",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "53dff327-ea63-45d3-9654-cd281f7588b3",
      "name": "Wait (Google jitter)",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2128,
        -112
      ],
      "parameters": {
        "unit": "seconds",
        "amount": "={{ (0.25 + Math.random()*0.75).toFixed(2) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "904715c5-ddb7-40cc-b256-1b7c80b5a736",
      "name": "Split In Batches (IndexNow \u2264500)",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -2976,
        320
      ],
      "parameters": {
        "options": {},
        "batchSize": "={{ Math.min(($items('Config')[0].json.BATCH_SIZE || 500), 500) }}"
      },
      "typeVersion": 3
    },
    {
      "id": "55fe2b25-1683-4761-9c22-f5f5bb880bfb",
      "name": "Build IndexNow payload",
      "type": "n8n-nodes-base.code",
      "position": [
        -2720,
        336
      ],
      "parameters": {
        "jsCode": "const batch = $items();\nif (!batch.length) return [];\n\n// 1) Pull & sanitize raw URLs\nlet urls = batch\n  .map(i => (i && i.json && i.json.loc) ? String(i.json.loc).trim() : null)\n  .filter(u => !!u); // remove null/empty\n\n// 2) Keep only absolute http/https\nurls = urls.filter(u => /^https?:\\/\\//i.test(u));\n\n// 3) Ensure they match your site host (precompute host once)\nconst siteUrl = $items('Config')[0].json.SITE_URL || '';\nconst m = siteUrl.match(/^https?:\\/\\/([^/]+)/i);\nconst host = m ? m[1] : '';\n\n// If you prefer to pass HOST explicitly in Config, replace host above with:\n// const host = $items('Config')[0].json.HOST;\n\nurls = urls.filter(u => {\n  const h = (u.match(/^https?:\\/\\/([^/]+)/i) || [])[1] || '';\n  return h.toLowerCase() === host.toLowerCase();\n});\n\n// 4) De-dupe\nurls = Array.from(new Set(urls));\n\n// 5) Guard: IndexNow expects at least 1 valid URL\nif (!urls.length || !host) return [];\n\nreturn [{\n  json: {\n    host,\n    key: $items('Config')[0].json.INDEXNOW_KEY,\n    keyLocation: $items('Config')[0].json.INDEXNOW_KEY_URL,\n    urlList: urls\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "78e5c981-997b-4918-9af5-579a79d1c208",
      "name": "IndexNow Submit",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2528,
        336
      ],
      "parameters": {
        "url": "https://api.indexnow.org/indexnow",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{ $json }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.1
    },
    {
      "id": "7355c015-2233-4fe4-9395-bc8044367ac1",
      "name": "Wait (IndexNow jitter)",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2336,
        336
      ],
      "parameters": {
        "unit": "seconds",
        "amount": "={{ (0.25 + Math.random()*0.75).toFixed(2) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "653fecfe-62f7-4cb3-9a28-2aee8db93bb9",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5952,
        -384
      ],
      "parameters": {
        "width": 416,
        "height": 688,
        "content": "## Config File\n**You MUST** update these variables:\n* SITE_URL\n* SITEMAP_URL\n* INDEXNOW_KEY: you can create it in the Bing Webmaster tools here https://www.bing.com/indexnow/getstarted \n* INDEXNOW_KEY_URL: it's usually your domain and the INDEXNOW_KEY: wwww.example.com/<INDEXNOW_KEY>\n\nVariables you can update depending on your specs:\n* DAYS_BACK: 7 by default. For Google it's checking the status of the page before submitting but for Indexnow it will ask to index all the pages that been last updated in the last 7 days \n* BATCH_SIZE: 500 it's the default recommended by IndexNow\n* USE_GOOGLE, USE_INDEXNOW: by default it's true which means the process will run for both Google and IndexNow\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1bc88f9f-1ae1-4d0e-afc2-817134d54d89",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5520,
        -192
      ],
      "parameters": {
        "color": 3,
        "width": 2160,
        "height": 496,
        "content": "## Parsing the sitemap and generating a list of the urls order by last modification date.\n* No need to edit anything here"
      },
      "typeVersion": 1
    },
    {
      "id": "2bf423e4-f723-401d-adab-610a5ce47a57",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3312,
        -592
      ],
      "parameters": {
        "color": 4,
        "width": 1360,
        "height": 720,
        "content": "## Google Autosubmitting\n* You need to update the \"Check status (Google)\" and \"URL updated (Google)\" node with your credentials\n* To create your credentials I recommend the following:\n** Create a service account here https://console.cloud.google.com/iam-admin/serviceaccounts\n** Permissions: Owner\n** Once created you go in the \"Keys\" tab, and create key. That will download a JSON file with your private key and the service account email you created\n* In the two nodes:\n** Authentication: Select \"Predefined credential type\"\n** Credential Type: Select \"Google Service Account API\"\n** Google Service Account API: Create new credential\n* To create the new credential:\n** Region: chose the one corresponding to your project\n** Service account email / Private Key: you can find it in the JSON key you downloaded from the Google Console\n** Toogle \"Set up for use in HTTP Request node\" and input https://www.googleapis.com/auth/indexing\n\n**Important** - once you have created a \"Service account email\" you need to add a user with this email and permission \"Owner\" in your Google Search Console: https://search.google.com/search-console/users"
      },
      "typeVersion": 1
    },
    {
      "id": "b03f0533-05de-4259-b3e9-d580dfd7e29c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3312,
        192
      ],
      "parameters": {
        "color": 5,
        "width": 1360,
        "height": 512,
        "content": "## IndexNow Autosubmitting\n* No need to change anything here"
      },
      "typeVersion": 1
    },
    {
      "id": "625b956c-7149-4963-9f19-fd4ab227db0e",
      "name": "Gate: Google",
      "type": "n8n-nodes-base.code",
      "position": [
        -3264,
        -144
      ],
      "parameters": {
        "jsCode": "// Gate: Google\nconst cfg = $items('Config')[0]?.json ?? {};\nconst on = (cfg.USE_GOOGLE === true) || String(cfg.USE_GOOGLE).toLowerCase() === 'true';\nreturn on ? $items() : [];"
      },
      "typeVersion": 2
    },
    {
      "id": "ac2028ca-4a70-414c-b962-cfb79f9f4f84",
      "name": "Gate: IndexNow",
      "type": "n8n-nodes-base.code",
      "position": [
        -3248,
        320
      ],
      "parameters": {
        "jsCode": "const cfg = $items('Config')[0]?.json ?? {};\nconst on = (cfg.USE_INDEXNOW === true) || String(cfg.USE_INDEXNOW).toLowerCase() === 'true';\nreturn on ? $items() : [];"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "06347cfd-c610-4edb-96d3-cf04ce434f75",
  "connections": {
    "Sort": {
      "main": [
        [
          {
            "node": "Assign mandatory sitemap fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Get sitemap.xml",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Sort",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gate: Google": {
      "main": [
        [
          {
            "node": "Loop Over Items (Google)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gate: IndexNow": {
      "main": [
        [
          {
            "node": "Split In Batches (IndexNow \u2264500)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get sitemap.xml": {
      "main": [
        [
          {
            "node": "Convert sitemap to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IndexNow Submit": {
      "main": [
        [
          {
            "node": "Wait (IndexNow jitter)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "is new? (Google)": {
      "main": [
        [
          {
            "node": "URL Updated (Google)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "URL Updated (Google)": {
      "main": [
        [
          {
            "node": "Wait (Google jitter)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait (Google jitter)": {
      "main": [
        [
          {
            "node": "Loop Over Items (Google)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check status (Google)": {
      "main": [
        [
          {
            "node": "is new? (Google)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "URL Updated (Google)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build IndexNow payload": {
      "main": [
        [
          {
            "node": "IndexNow Submit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait (IndexNow jitter)": {
      "main": [
        [
          {
            "node": "Split In Batches (IndexNow \u2264500)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert sitemap to JSON": {
      "main": [
        [
          {
            "node": "Get content-specific sitemaps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items (Google)": {
      "main": [
        [],
        [
          {
            "node": "Check status (Google)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Force urlset.url to array": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "convert page data to JSON": {
      "main": [
        [
          {
            "node": "Force urlset.url to array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get content of each sitemap": {
      "main": [
        [
          {
            "node": "convert page data to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get content-specific sitemaps": {
      "main": [
        [
          {
            "node": "Get content of each sitemap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \"Test workflow\"": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assign mandatory sitemap fields": {
      "main": [
        [
          {
            "node": "Filter: lastmod within DAYS_BACK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: lastmod within DAYS_BACK": {
      "main": [
        [
          {
            "node": "Gate: Google",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gate: IndexNow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches (IndexNow \u2264500)": {
      "main": [
        [],
        [
          {
            "node": "Build IndexNow payload",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Site owners, SEOs, and marketers who want a single automation to notify Google (Indexing API) and Bing (via IndexNow) whenever site URLs are added or updated. No more need to update it manually. Hours saved

Source: https://n8n.io/workflows/8778/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow automates the collection of public procurement data from TenderNed (the official Dutch tender platform). It: Fetches the latest tender publications from the TenderNed API Retrieves detai

HTTP Request, XML, Data Table
Web Scraping

This workflow fetches reports from Qualys, filters out already processed reports, and creates cases in TheHive for the new reports. It runs every hour to ensure continuous monitoring and up-to-date vu

HTTP Request, n8n, XML +1
Web Scraping

This workflow helps SEO professionals and website owners automate the tedious process of monitoring and indexing URLs. It fetches your XML sitemap, filters for recent content, checks the current index

HTTP Request, XML
Web Scraping

Google page indexing too slow? Tired of manually clicking through each page in the Google Search Console? 😴 Say goodbye to that tedious process and hello to automation with this n8n workflow! 🎉

HTTP Request, XML
Web Scraping

This n8n workflow automates the process of crawling a website's sitemap to extract URLs, which is particularly useful for SEO analysis, website auditing, or content monitoring. By leveraging n8n's nod

XML, HTTP Request