AutomationFlowsWeb Scraping › Automated SEO Indexing: Sitemap to Gsc & Indexing API

Automated SEO Indexing: Sitemap to Gsc & Indexing API

ByHàn Thiên Hải @hanthienhai on n8n.io

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 indexing status via Google Search Console (GSC), and automatically submits…

Event trigger★★★★☆ complexity21 nodesHTTP RequestXML
Web Scraping Trigger: Event Nodes: 21 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #11979 — 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": "gfwrZIQMBNjI9x3A",
  "name": "Automated SEO Indexing: Sitemap to GSC & Indexing API",
  "tags": [],
  "nodes": [
    {
      "id": "96e7f03e-e08d-4256-b6f5-e99946cb7d72",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        1024,
        688
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "304c6dc5-e1f7-4167-b9e5-361ba3d69026",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        1248,
        592
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ffc58500-4c70-49f6-87c5-8d9f6e547690",
              "name": "sitemap",
              "type": "string",
              "value": "https://gamelandvn.com/sitemap_index.xml"
            },
            {
              "id": "9f4adba7-e964-47d4-b420-34e9ee94e7d4",
              "name": "gsc_property",
              "type": "string",
              "value": "https://gamelandvn.com/"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fa4d8a14-76c5-4b18-8116-eae4544830bc",
      "name": "Fetch Sitemap XML",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1472,
        592
      ],
      "parameters": {
        "url": "={{ $json.url || $node[\"Configuration\"].json.sitemap }}",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "b4b296be-37f3-4f5e-8c75-c250547b8f9b",
      "name": "Convert XML to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        1696,
        512
      ],
      "parameters": {
        "options": {
          "trim": true,
          "normalize": true,
          "mergeAttrs": true,
          "ignoreAttrs": false,
          "normalizeTags": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b07b709f-25de-4ce8-a2f3-8a602953a30a",
      "name": "Parse Sitemap Structure",
      "type": "n8n-nodes-base.code",
      "position": [
        1920,
        512
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nlet output = [];\n\n// Sitemap Index\nif (input.sitemapindex && input.sitemapindex.sitemap) {\n    const sitemaps = Array.isArray(input.sitemapindex.sitemap) \n        ? input.sitemapindex.sitemap \n        : [input.sitemapindex.sitemap];\n    \n    output = sitemaps.map(s => ({\n        url: s.loc,\n        isSitemapIndex: true\n    }));\n} \n// Single sitemap\nelse if (input.urlset && input.urlset.url) {\n    const urls = Array.isArray(input.urlset.url) \n        ? input.urlset.url \n        : [input.urlset.url];\n    \n    output = urls.map(u => ({\n        urlData: u,\n        isSitemapIndex: false\n    }));\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "e1044373-f078-4a7a-941f-3edc34c6fd8e",
      "name": "Is Sitemap Index?",
      "type": "n8n-nodes-base.if",
      "position": [
        2144,
        592
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3b4d5079-327b-487e-a553-b595532ddf06",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isSitemapIndex }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "efd97ee2-d810-4463-a14f-a974d158c37e",
      "name": "Format URL Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2480,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "url-field",
              "name": "url",
              "type": "string",
              "value": "={{ $json.urlData.loc }}"
            },
            {
              "id": "lastmod-field",
              "name": "lastmod",
              "type": "string",
              "value": "={{ $json.urlData.lastmod || '' }}"
            },
            {
              "id": "current-date",
              "name": "currentDate",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "0d9aa95e-a433-4670-892c-f605bec062ba",
      "name": "Filter: Recent URLs Only",
      "type": "n8n-nodes-base.filter",
      "position": [
        2704,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-lastmod",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.lastmod }}",
              "rightValue": ""
            },
            {
              "id": "check-date-range",
              "operator": {
                "type": "dateTime",
                "operation": "after",
                "singleValue": true
              },
              "leftValue": "={{ $json.lastmod }}",
              "rightValue": "={{ $now.minus({ days: 90 }).toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4a8a07b1-4848-43bb-801e-aa45336a8646",
      "name": "Check Submission History",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        608
      ],
      "parameters": {
        "jsCode": "// Load previously submitted URLs from storage\nconst items = [];\n\nfor (const item of $input.all()) {\n  const url = item.json.url;\n  let shouldSubmit = true;\n  let reason = 'new_url';\n  \n  try {\n    // Check if URL was submitted before\n    const storageKey = `indexed:${Buffer.from(url).toString('base64').substring(0, 50)}`;\n    const result = await $execution.getWorkflowStaticData('global');\n    \n    if (result && result[storageKey]) {\n      const lastSubmitted = new Date(result[storageKey]);\n      const daysSinceSubmit = Math.floor((Date.now() - lastSubmitted.getTime()) / (1000 * 60 * 60 * 24));\n      \n      // Only resubmit if more than 7 days have passed\n      if (daysSinceSubmit < 7) {\n        shouldSubmit = false;\n        reason = `submitted_${daysSinceSubmit}_days_ago`;\n      } else {\n        reason = 'resubmit_after_7_days';\n      }\n    }\n  } catch (error) {\n    // If storage fails, default to submitting\n    reason = 'storage_error';\n  }\n  \n  items.push({\n    json: {\n      ...item.json,\n      shouldSubmit,\n      reason\n    }\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "a242b268-1079-43b9-b417-8f1542701422",
      "name": "GSC: Inspect URL Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3504,
        608
      ],
      "parameters": {
        "url": "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect",
        "method": "POST",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 2000
            }
          }
        },
        "jsonBody": "={\n  \"inspectionUrl\": \"{{ $json.url }}\",\n  \"siteUrl\": \"{{ $('Configuration').item.json.gsc_property }}\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true
    },
    {
      "id": "c6127880-6072-4d00-aef4-3b645dea9901",
      "name": "Logic: Should Index?",
      "type": "n8n-nodes-base.code",
      "position": [
        3728,
        608
      ],
      "parameters": {
        "jsCode": "// Check if URL needs indexing based on Search Console response\nconst items = [];\n\nfor (const item of $input.all()) {\n  let needsIndexing = true;\n  let indexStatus = 'unknown';\n  \n  try {\n    const response = item.json;\n    \n    // Check coverage state\n    if (response.inspectionResult && response.inspectionResult.indexStatusResult) {\n      const coverageState = response.inspectionResult.indexStatusResult.coverageState;\n      indexStatus = coverageState;\n      \n      // If already indexed and no issues, skip\n      if (coverageState === 'Submitted and indexed' || coverageState === 'Indexed, not submitted in sitemap') {\n        needsIndexing = false;\n      }\n    }\n  } catch (error) {\n    // If check fails, default to indexing\n    indexStatus = 'check_failed';\n  }\n  \n  items.push({\n    json: {\n      url: item.json.url || item.json.inspectionResult?.inspectionUrl,\n      needsIndexing,\n      indexStatus,\n      originalData: item.json\n    }\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "232a6d6f-0abd-4b97-b15f-affaebfcff5a",
      "name": "Filter: Only Not Indexed",
      "type": "n8n-nodes-base.filter",
      "position": [
        3952,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-indexing-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.needsIndexing }}",
              "rightValue": true
            },
            {
              "id": "f8579e5d-e842-40cf-9bf5-cc94de7e05d5",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.originalData.inspectionResult.indexStatusResult.indexingState }}",
              "rightValue": "INDEXING_STATE_UNSPECIFIED"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "06a009bd-e21d-49ed-9d3b-52c7f17801d7",
      "name": "Batch Processing",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4176,
        608
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "6fdc1d5f-b3a2-4719-99b1-776ae927b138",
      "name": "Delay (Rate Limiting)",
      "type": "n8n-nodes-base.wait",
      "position": [
        4624,
        608
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 2
      },
      "typeVersion": 1
    },
    {
      "id": "ec8043b3-5afa-4701-af3b-b9f2056cbe79",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1024,
        496
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "5740f5e3-64be-4535-aa46-0774ce91a4f3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 576,
        "content": "# SETUP & CONFIGURATION"
      },
      "typeVersion": 1
    },
    {
      "id": "146b8248-3146-4a7b-abd1-5ccd210b3702",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 576,
        "content": "# DATA FILTERING & LOGIC"
      },
      "typeVersion": 1
    },
    {
      "id": "ce12a6a2-9f47-4604-9fbe-e8ef8b59c85d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 576,
        "content": "# API INTERACTION & INDEXING"
      },
      "typeVersion": 1
    },
    {
      "id": "93247ec5-f7ab-46e3-8c67-fe4326c4f228",
      "name": "Filter: Valid for Submission",
      "type": "n8n-nodes-base.filter",
      "position": [
        3280,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "should-submit-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.shouldSubmit }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "287fc7ca-6e67-4371-88b3-ac468f986bed",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 832,
        "height": 1072,
        "content": "## Overview\n\nThis workflow automates Google indexing by fetching URLs from your sitemap, checking their status in GSC, and submitting non-indexed pages to the Google Indexing API.\n\n## Setup steps\n\n**1. Configure Google Cloud Console**\n\nCreate Project: Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project.\n\nEnable APIs: Search for and enable both the Google Search Console API and the Web Search Indexing API.\n\nCreate Service Account: Navigate to Credentials > Create Credentials > Service Account. Fill in the details and click Done.\n\nGenerate Key: Select your service account > Edit service account > Keys > Add Key > Create new key > JSON. Save the downloaded file securely.\n\n**2. Set Up Credentials in n8n**\n\nNew Credential: In n8n, go to Create credentials > Google Service Account API.\n\nInput Data: Paste the Service Account Email and Private Key from your downloaded JSON file. In JSON file, Service Account Email is client_email and Private Key is private_key.\n\nHTTP Request Setup: Enable Set up for use in HTTP Request node.\n\nScopes: Enter exactly: https://www.googleapis.com/auth/indexing https://www.googleapis.com/auth/webmasters.readonly into the Scopes field.\n\nGSC Permission: Add the Service Account email to your Google Search Console property as an Owner via Settings > Users and permissions > Add user.\n\n**3. Workflow Configuration**\n\nConfiguration Node: Open the Configuration node and enter your Sitemap URL and GSC Property URL. If your property type is URL prefix, the URL must end with a forward slash /. Example: https://hanthienhai.com/\n\nLink Credentials: Update the credentials in both the GSC: Inspect URL Status and GSC: Request Indexing nodes with the service account created in Step 2.\n\n**4. Schedule & Activate**\n\nSet Schedule: Adjust the Schedule Trigger node to your preferred execution frequency.\n\nActivate: Toggle the workflow to Active to start the automation.\n\n## Questions or Need Help?\n\nFor setup assistance, customization, or workflow support, feel free to contact me at admin@hanthienhai.com"
      },
      "typeVersion": 1
    },
    {
      "id": "9c1cae13-9ea1-4fd0-b631-b394f894047d",
      "name": "GSC: Request Indexing",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4400,
        528
      ],
      "parameters": {
        "url": "https://indexing.googleapis.com/v3/urlNotifications:publish",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"url\": \"{{ $json.originalData.inspectionResult.indexStatusResult.userCanonical }}\",\n  \"type\": \"URL_UPDATED\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true,
      "alwaysOutputData": true
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0a710726-0b64-42bd-8a88-630c34467c28",
  "connections": {
    "Configuration": {
      "main": [
        [
          {
            "node": "Fetch Sitemap XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format URL Data": {
      "main": [
        [
          {
            "node": "Filter: Recent URLs Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Processing": {
      "main": [
        [],
        [
          {
            "node": "GSC: Request Indexing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Sitemap XML": {
      "main": [
        [
          {
            "node": "Convert XML to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Sitemap Index?": {
      "main": [
        [
          {
            "node": "Fetch Sitemap XML",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format URL Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert XML to JSON": {
      "main": [
        [
          {
            "node": "Parse Sitemap Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Logic: Should Index?": {
      "main": [
        [
          {
            "node": "Filter: Only Not Indexed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delay (Rate Limiting)": {
      "main": [
        [
          {
            "node": "Batch Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Request Indexing": {
      "main": [
        [
          {
            "node": "Delay (Rate Limiting)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Inspect URL Status": {
      "main": [
        [
          {
            "node": "Logic: Should Index?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Sitemap Structure": {
      "main": [
        [
          {
            "node": "Is Sitemap Index?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Submission History": {
      "main": [
        [
          {
            "node": "Filter: Valid for Submission",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Only Not Indexed": {
      "main": [
        [
          {
            "node": "Batch Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Recent URLs Only": {
      "main": [
        [
          {
            "node": "Check Submission History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Valid for Submission": {
      "main": [
        [
          {
            "node": "GSC: Inspect URL Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Configuration",
            "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

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 indexing status via Google Search Console (GSC), and automatically submits…

Source: https://n8n.io/workflows/11979/ — 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

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

HTTP Request, XML
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

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