TryGhost/Ghost · error · Error
Failed to verify code
Error message
Failed to verify code
What it means
Thrown in api.member.verifyOTC (apps/portal/src/utils/api.js:402) when POST /members/api/verify-otc/ returns non-ok and HumanReadableError cannot be parsed. Message 'Failed to verify code'. Covers the one-time-code verification step of the new sign-in flow.
Source
Thrown at apps/portal/src/utils/api.js:402
};
const res = await makeRequest({
url,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (res.ok) {
return await res.json();
} else {
const humanError = await HumanReadableError.fromApiResponse(res);
if (humanError) {
throw humanError;
}
throw new Error('Failed to verify code');
}
},
signout(all = false) {
const url = endpointFor({type: 'members', resource: 'session'});
return makeRequest({
url,
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
all
})
}).then(function (res) {
if (res.ok) {
window.location.replace(siteUrl);
return 'Success';View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Have the user request a fresh magic link and use the new code.
- Inspect the response status — 400/422 = wrong code, 429 = slow down, 404 = otc_ref invalid.
- Confirm Portal and Ghost server versions both support the OTC verify contract.
- Ensure the integrity token is still valid when verify-otc is called.
Example fix
// before
throw new Error('Failed to verify code');
// after
const e = new Error(`Failed to verify code (${res.status})`);
e.status = res.status;
throw e; Defensive patterns
Strategy: validation
Validate before calling
// 6-digit code shape check
function isValidOtc(code) {
return typeof code === 'string' && /^\d{6}$/.test(code.trim());
} Try / catch
try {
const result = await api.member.verifyOTC({otc, otcRef, integrityToken});
return result;
} catch (err) {
if (err.code === 'rate_limited') { notifyWait(); }
else { notifyError('Wrong or expired code — request a new link'); }
} Prevention
- Validate the OTC shape (6 digits) before submitting.
- Rate-limit verification attempts client-side to avoid 429s.
- Re-mint the magic link when the code expires instead of guessing.
- Confirm Portal and Ghost server both support the OTC verify contract.
When it happens
Trigger: Member enters the one-time code from their email; verify-otc responds 4xx/5xx without a JSON error body — typically a wrong/expired code (400), rate-limited guesses (429), or the otc_ref no longer matches a pending session.
Common situations: User typed the code wrong too many times; the code expired (usually >10 min); the otc_ref session was cleared; replay attempt of an already-used code; Portal bundle / server version mismatch in the OTC flow.
Related errors
- Failed to send magic link email
- Failed to update newsletter
- Failed to update member
- Failed to fetch site data
- fallbackMessage
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/c70903a12d3f3180.
Report an issue: GitHub.