Significant-Gravitas/AutoGPT · error · Error
The ChatGPT verification link is invalid. Close this window
Error message
The ChatGPT verification link is invalid. Close this window and try again.
What it means
This is an HTTP 401 Unauthorized raised by APIKeyAuthenticator, a FastAPI security dependency that validates an API-key header on incoming requests. The header was present (a missing header produces the distinct 'No API key in request' error at line 86-88), but the validator returned a falsy result: either the custom validator rejected the key, or the default_validator's secrets.compare_digest mismatch against expected_token. In this repo the concrete production use is the Postmark webhook router (backend/api/features/postmark/postmark.py:31), which compares the X-Postmark-Webhook-Token header to settings.secrets.postmark_webhook_token.
Source
Thrown at autogpt_platform/backend/backend/api/features/integrations/codex_device_page.py:79
const statusElement = document.getElementById("status");
const errorElement = document.getElementById("error");
const copyButton = document.getElementById("copy-code");
function fail(message) {
statusElement.textContent = "";
errorElement.textContent = message;
}
window.addEventListener("pagehide", function () {
if (!finished) {
navigator.sendBeacon(window.location.pathname + "/cancel");
}
});
let verificationUrl;
try {
verificationUrl = new URL(rawVerificationUrl);
if (verificationUrl.protocol !== "https:") throw new Error();
} catch (_) {
fail("The ChatGPT verification link is invalid. Close this window and try again.");
return;
}
if (!state || !userCode || !loginID) {
fail("This sign-in attempt is incomplete. Close this window and try again.");
return;
}
codeElement.textContent = userCode;
linkElement.href = verificationUrl.toString();
copyButton.addEventListener("click", async function () {
try {
await navigator.clipboard.writeText(userCode);
copyButton.textContent = "Copied";
} catch (_) {
fail("Could not copy automatically. Select the code above instead.");
}View on GitHub (pinned to 9c8bb5550f)
Solutions
- Compare the exact token value: check the X-Postmark-Webhook-Token header you are sending against settings.secrets.postmark_webhook_token (usually the POSTMARK_WEBHOOK_TOKEN env var in backend/.env or docker-compose env) — they must match character-for-character with no whitespace or quotes.
- If the backend log shows the warning 'API key check failed: ...' from api_key_auth.py:119, the expected token is None/empty or non-ASCII: set a valid non-empty ASCII token in the backend environment (backend/.env → secrets section) and restart the service.
- If the token was rotated in the Postmark dashboard, copy the new webhook token into the same env var on every deployment that receives the webhook, then restart.
- Verify you are hitting the right environment: staging and production often run different secrets; confirm the URL configured in Postmark points at the deployment whose env you inspected.
- For local reproduction, curl -X POST http://localhost:8000/api/postmark/ -H 'X-Postmark-Webhook-Token: <token>' -H 'Content-Type: application/json' -d '{...}' and confirm a non-401 response before re-configuring Postmark.
Example fix
# before: backend/.env has a placeholder or nothing POSTMARK_WEBHOOK_TOKEN= # after: set the exact token shown in Postmark > Signings > Webhooks (no quotes/spaces) POSTMARK_WEBHOOK_TOKEN=a1b2c3d4e5f6g7h8 # then restart the backend so Settings() picks it up # docker compose restart backend (or: poetry run app)
Defensive patterns
Strategy: validation
Validate before calling
# Before sending/ configuring, verify the token is usable (server side, Python)
import os
POSTMARK_TOKEN = os.environ.get("POSTMARK_WEBHOOK_TOKEN", "")
assert POSTMARK_TOKEN and POSTMARK_TOKEN.isascii() and POSTMARK_TOKEN == POSTMARK_TOKEN.strip(), (
"POSTMARK_WEBHOOK_TOKEN must be a non-empty, ASCII, whitespace-free value"
)
# Client side: assert the header you are about to send is non-empty and ASCII
# before making the request
# assert token and token.isascii(), 'webhook token missing or non-ASCII' Try / catch
# Python client: distinguish 'invalid key' (fix the token) from 'missing key' (fix the header)
import requests
resp = requests.post(url, headers={"X-Postmark-Webhook-Token": token}, json=payload, timeout=10)
if resp.status_code == 401:
detail = resp.json().get("detail")
if detail == "No API key in request":
raise RuntimeError("Header X-Postmark-Webhook-Token is not being sent") from None
if detail == "Invalid API key":
raise RuntimeError("Token mismatch: rotate/re-copy POSTMARK_WEBHOOK_TOKEN") from None
resp.raise_for_status() Prevention
- Treat the webhook token as provisioned config: set POSTMARK_WEBHOOK_TOKEN in backend/.env (or docker-compose environment) at deploy time and fail startup if it is empty when the Postmark router is enabled.
- Never copy tokens with surrounding quotes or spaces; strip them on entry and validate the value is non-empty ASCII.
- After any token rotation in the Postmark dashboard, update every environment (staging, prod) that receives webhooks and restart the service, since Settings() reads secrets at startup.
- In tests, exercise both failure modes (wrong token -> 401 'Invalid API key', absent header -> 401 'No API key in request') as done in backend/backend/api/utils/api_key_auth_test.py so regressions in header name or comparison are caught early.
- Monitor for the 'API key check failed' warning from api_key_auth.py:119 — it indicates a misconfigured (None/non-ASCII) expected token rather than an attacker, so alert on it separately from auth failures.
When it happens
Trigger: Calling POST /api/postmark/ (or any route secured with Security(api_key_auth)) with an X-Postmark-Webhook-Token header whose value does not exactly match the configured token. Also triggered when the server-side token setting is None or empty and compare_digest raises TypeError (line 117-120 logs a warning and returns False), or when the header value contains non-ASCII characters, which likewise makes compare_digest raise TypeError and get converted to a validation failure.
Common situations: The POSTMARK_WEBHOOK_TOKEN secret is not set (or is set to a placeholder like 'changeme') in backend/.env on the server while a correct token is configured in Postmark's webhook settings; the token was rotated in Postmark but not in the deployment env; trailing whitespace, quotes, or encoding issues from copy-pasting the token into .env or the Postmark dashboard; sending the token in the wrong header (e.g. Authorization or X-API-Key instead of X-Postmark-Webhook-Token gets the 'No API key' 401 instead); multiple deployments (staging vs prod) with different tokens.
Related errors
- No API key in request
- Authentication failed — please sign in again.
- Authentication error — please sign in again.
- User not found.
- OpenAI API key not configured
AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14).
Data as JSON: /api/errors/6ee1112a5c872949.
Report an issue: GitHub.