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

  1. Have the user request a fresh magic link and use the new code.
  2. Inspect the response status — 400/422 = wrong code, 429 = slow down, 404 = otc_ref invalid.
  3. Confirm Portal and Ghost server versions both support the OTC verify contract.
  4. 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

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


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/c70903a12d3f3180. Report an issue: GitHub.