Fixed: "Site Verification is not completed" (403 SiteVerificationNotCompleted) in IndexNow
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:
{
"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:
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
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}

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:
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:
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:
// 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());
}
}

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:
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

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
| Mistake | What happens | Do this instead |
|---|---|---|
| Reading only the status code | A 403 looks like a bad key | Read errorCode in the body |
Making a new key because of this 403 | The new key has to go on the site and be checked from the start | Keep the key, check its file, and send again in a few minutes |
| Sending again in a tight loop | You risk 429 Too Many Requests | Wait a minute or more between tries |
| Using the example key from Bing's get-started page | Your site doesn't serve that key's file, so the key isn't found | Use the key your site serves at /<key>.txt |
Failing a deploy job on this 403 | The job fails every time the key is new | Wait and send again for a few minutes |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
403 with SiteVerificationNotCompleted | The search engine hasn't checked a new key yet | Wait a few minutes and send the same request again |
Still SiteVerificationNotCompleted after about 15 minutes | The search engine can't fetch the key file | Fetch https://<your-site>/<key>.txt from outside your network. It must answer 200 with only the key |
403 with another errorCode | The key in the request doesn't match the file, or the file is missing | Compare the key field with the file's name and its contents |
422 Unprocessable Entity | A URL isn't on the host you named, or the key's format is wrong | Send only URLs on that host |
429 Too Many Requests | Too many requests | Wait at least 10 minutes before you send the same URLs again |
202 Accepted | Received, and the key check is pending | Nothing: 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
- IndexNow documentation — the request format, the key file and the response codes.
- IndexNow FAQ — key verification, what to submit, and limits.
- Get started with IndexNow on Bing — Bing's steps, from the key to checking your submissions in Bing Webmaster Tools.
