Skip to main content

Fixed: "Site Verification is not completed" (403 SiteVerificationNotCompleted) in IndexNow

· 11 min read
Jagdish Kumawat
Founder @ Dewiride

The first time I sent this site's URLs to IndexNow, it answered 403 with SiteVerificationNotCompleted, though the key file was live and correct. Five minutes later, the same request went through.

What I Was Doing​

IndexNow is a way to tell search engines that pages on your site were added, changed or removed, so they can crawl them now instead of on their next visit. To use it, you put a key file at the root of your site: a text file named after the key, which holds the key. Search engines fetch that file to confirm that the site is yours.

This site has had its key file since July 2025, but nothing in its code had ever sent URLs with the key. So I sent all 1,555 URLs from its sitemap in one request, the way Bing's get-started page shows. The request is a POST to api.indexnow.org with a JSON body: host is the site, key is the key, keyLocation is the key file's address, and urlList holds the URLs. I built the body from the sitemap:

payload.json (the first 3 of 1,555 URLs)
{
"host": "devblogs.dewiride.com",
"key": "0123456789abcdef0123456789abcdef",
"keyLocation": "https://devblogs.dewiride.com/0123456789abcdef0123456789abcdef.txt",
"urlList": [
"https://devblogs.dewiride.com/about",
"https://devblogs.dewiride.com/blog",
"https://devblogs.dewiride.com/blog/adding-secrets-azure-key-vault-step-by-step-portal-coding-guide"
]
}

The key in this post is a placeholder. Then I sent the body with curl:

Terminal
curl -sS --max-time 90 -X POST "https://api.indexnow.org/indexnow" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary @payload.json \
-D response-headers.txt -o response-body.txt \
-w 'HTTP %{http_code} (%{time_total}s, uploaded %{size_upload} bytes)\n'
cat response-body.txt

-sS keeps curl quiet but still shows errors, and --max-time 90 gives up after 90 seconds. -X POST and -H make it a JSON POST, and --data-binary @payload.json sends the file exactly as it is. -D and -o save the response's headers and body to files, and -w prints the status code, the time it took and the bytes sent. cat then shows the body.

What I Expected​

The IndexNow documentation says: "A successful request will return an HTTP 200 response code." For a first request, the IndexNow FAQ adds that "you may receive an HTTP 202 response" while the search engine checks the key. So I expected 200 or 202.

What I Got​

Output
HTTP 403 (1.737396s, uploaded 125507 bytes)
{"errorCode":"SiteVerificationNotCompleted","message":"Site Verification is not completed. Please wait for some time for the verification to complete and try again.","details":null}

A terminal where curl sends a POST to api.indexnow.org and gets HTTP 403, with the body errorCode SiteVerificationNotCompleted and the message Site Verification is not completed.

IndexNow lists 403 Forbidden as "In case of key not valid (e.g. key not found, file found but key not in the file)". So at first sight, my key looked wrong.

What I Checked​

The key file, the way a crawler sees it. I fetched it with Bingbot's user agent, so the server would answer the way it answers Bing. Put your own key in KEY:

Terminal
KEY=0123456789abcdef0123456789abcdef
curl -sS -A "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" \
-w '\nHTTP %{http_code} %{content_type} %{size_download} bytes\n' \
"https://devblogs.dewiride.com/$KEY.txt"

-A sets the user agent, and -w prints the status code, the content type and the size after the file itself:

Output
0123456789abcdef0123456789abcdef
HTTP 200 text/plain 32 bytes

The file was there, as plain text, holding the key and nothing else: 32 characters, with no line break. So the key wasn't "not found", and it wasn't "not in the file".

What the body says. The body is more specific than the status code. Its errorCode is SiteVerificationNotCompleted, and its message asks me to wait for the verification to complete and try again. Bing wasn't saying that the key was wrong. It was saying that it hadn't finished checking it.

The official documentation. The IndexNow documentation has a status code for this state, and it's not 403. It's 202: "URL received. IndexNow key validation pending." Neither the documentation nor the FAQ mentions SiteVerificationNotCompleted, and I found no official page that does.

Why It Happens​

A search engine doesn't check a key when you put the file on your site. It checks it when a request uses the key. The IndexNow documentation says: "Once you submit your URLs to search engines, search engines will crawl the key file to verify ownership and use the key until you change the key."

This key had been on the site for months, but nothing in the site's code had ever sent URLs with it. When my request arrived, Bing hadn't checked the file yet, as its message said. Instead of the 202 the documentation describes, it refused the request with 403, and its error code said why. Nothing was wrong with the key or the file.

The Fix​

First, rule out a real key problem with the check above. If the key file answers 200 with exactly your key, there's nothing to change. Wait a few minutes, then send the same request again.

I had a small script send the identical request at 18:02 UTC, five minutes after the first, and again a few minutes apart after that if it still failed. It stopped at the first answer that wasn't this 403.

Let Automation Wait for It​

A deploy job that sends URLs to IndexNow meets this answer the first time it uses a key, and again whenever you change the key: the FAQ says "Search engines will automatically re-verify ownership when the updated key is detected during URL submission." So treat it as "wait and send again", not as a failure.

That's what this site's deploy job does. It sends again once a minute, for up to ten minutes, and fails at once on any other 403:

scripts/indexnow.mjs (excerpt)
// The first time Bing sees a key, it answers 403 SiteVerificationNotCompleted until
// it has checked the key file. On 2026-11-08 that took under five minutes. Wait up
// to ten, sending again once a minute.
const VERIFICATION_RETRY_MS = 60_000;
const VERIFICATION_WAIT_MS = 10 * 60_000;

function errorCodeOf(body) {
try {
return JSON.parse(body)?.errorCode;
} catch {
return undefined;
}
}

async function submit(urlList) {
const body = JSON.stringify({host: SITE.host, key: KEY, keyLocation: KEY_LOCATION, urlList});
const giveUpAt = Date.now() + VERIFICATION_WAIT_MS;
for (;;) {
const response = await request(ENDPOINT, {
method: 'POST',
headers: {'Content-Type': 'application/json; charset=utf-8'},
body,
});
// 200: received. 202: received, and the key check is still pending.
if (response.status === 200 || response.status === 202) return response.status;
const text = (await response.text()).trim();
const verifying = response.status === 403 && errorCodeOf(text) === 'SiteVerificationNotCompleted';
if (verifying && Date.now() < giveUpAt) {
console.log(
`IndexNow has not finished checking the key (403 SiteVerificationNotCompleted). ` +
`Sending again in ${VERIFICATION_RETRY_MS / 1000}s.`,
);
await sleep(VERIFICATION_RETRY_MS);
continue;
}
const hint = verifying
? `IndexNow was still checking the key after ${VERIFICATION_WAIT_MS / 60_000} minutes. Re-run this job later.`
: (HINTS[response.status] ?? '');
throw new Error(`${ENDPOINT} answered HTTP ${response.status}${text ? ` ${text}` : ''}. ${hint}`.trim());
}
}

A macOS-style code window with the submit function from indexnow.mjs, which sends the request again once a minute while IndexNow answers 403 SiteVerificationNotCompleted, for up to ten minutes, and fails on any other answer.

errorCodeOf reads errorCode from the body, so the job can tell this 403 apart from a bad key. giveUpAt is when the job stops waiting. Every other answer becomes an error that says what to check.

After the Fix​

The first resend went through:

Output
2026-11-08T17:59:49Z attempt 2: waiting 130s until 18:02:00Z
2026-11-08T18:02:02Z attempt 2: HTTP 200 (empty body)
2026-11-08T18:02:02Z RESULT: submitted

A terminal log where the same IndexNow request, sent again at 18:02 UTC, gets HTTP 200 with an empty body, and the script reports it as submitted.

200, with an empty body: IndexNow had received all 1,555 URLs, five minutes after it refused them. A 200 doesn't mean the pages are indexed. In the documentation's words, "the HTTP 200 response code only indicates that the search engine has received your set of URLs." The search engines still decide when to crawl them.

Common Mistakes​

MistakeWhat happensDo this instead
Reading only the status codeA 403 looks like a bad keyRead errorCode in the body
Making a new key because of this 403The new key has to go on the site and be checked from the startKeep the key, check its file, and send again in a few minutes
Sending again in a tight loopYou risk 429 Too Many RequestsWait a minute or more between tries
Using the example key from Bing's get-started pageYour site doesn't serve that key's file, so the key isn't foundUse the key your site serves at /<key>.txt
Failing a deploy job on this 403The job fails every time the key is newWait and send again for a few minutes

Troubleshooting​

SymptomLikely causeFix
403 with SiteVerificationNotCompletedThe search engine hasn't checked a new key yetWait a few minutes and send the same request again
Still SiteVerificationNotCompleted after about 15 minutesThe search engine can't fetch the key fileFetch https://<your-site>/<key>.txt from outside your network. It must answer 200 with only the key
403 with another errorCodeThe key in the request doesn't match the file, or the file is missingCompare the key field with the file's name and its contents
422 Unprocessable EntityA URL isn't on the host you named, or the key's format is wrongSend only URLs on that host
429 Too Many RequestsToo many requestsWait at least 10 minutes before you send the same URLs again
202 AcceptedReceived, and the key check is pendingNothing: the URLs were received

FAQ​

How long does the check take?​

For this site, less than five minutes. The first request went at 17:57 UTC and was refused, and the same request at 18:02 went through. I found no official figure.

Do I have to do something in Bing Webmaster Tools?​

Not for this. The second request went through without any change in Bing Webmaster Tools. IndexNow confirms that the site is yours through the key file.

Should I send every URL of my site?​

Usually not. The IndexNow FAQ says IndexNow "is intended for notifying search engines about recently added, updated, or deleted URLs. It is not designed for submitting every URL on your site at once." It allows all of them after a site-wide change, such as a migration or a redesign, and points to an XML sitemap for everything else. Since this first request, this site sends only the pages each deploy adds, changes or removes.

Do I have to send the URLs to each search engine?​

No. The FAQ says that your submission "will be shared across all IndexNow-enabled search engines", whichever of their endpoints you send it to.

Conclusion​

A 403 with SiteVerificationNotCompleted doesn't mean your key is wrong. It means the search engine hasn't finished checking it. Make sure the key file answers 200 with exactly the key, wait a few minutes, and send the same request again. If a deploy job sends your URLs, let it wait and send again on this one answer, instead of failing.

Additional Resources​