toeverything/AFFiNE · error · CaptchaVerificationFailed
captcha_verification_failed
captcha_verification_failed
Error message
Invalid Credential
What it means
Thrown by CaptchaService.assertValidCredential when the raw credential object passed in fails the zod validator — wrong shape, missing required fields (provider, token, challenge), or wrong types. The service normalizes any malformed credential into CaptchaVerificationFailed('Invalid Credential') so callers get one clean error instead of zod internals, and counts it under the 'invalid_credential' metric.
Source
Thrown at packages/backend/server/src/plugins/captcha/service.ts:177
provider,
challenge,
resource,
};
}
assertValidCredential(credential: any): Credential {
try {
return validator.parse(credential);
} catch {
metrics.auth.counter('captcha_verification').add(1, {
provider:
credential?.provider === 'hashcash' ||
credential?.provider === 'turnstile'
? credential.provider
: 'unknown',
result: 'invalid_credential',
});
throw new CaptchaVerificationFailed('Invalid Credential');
}
}
async verifyRequest(credential: Credential, req: Request) {
if (credential.provider === 'hashcash') {
if (!credential.challenge) {
metrics.auth.counter('captcha_verification').add(1, {
provider: 'hashcash',
result: 'missing_challenge',
});
throw new CaptchaVerificationFailed('Missing Challenge');
}
const resource = await this.challenges.consume<string>(
'captcha',
credential.challenge
);
if (!resource) {
metrics.auth.counter('captcha_verification').add(1, {View on GitHub (pinned to b4c8548c09)
Solutions
- Log/inspect the credential object at the boundary and compare it to the expected Credential schema (provider: 'hashcash'|'turnstile', token: string, optional challenge).
- Use the official client-side captcha helper to mint the credential (Turnstile widget or the hashcash challenge flow) before calling the protected endpoint.
- Fix payload key names in custom clients (e.g. token vs response) to match the schema.
- Ensure the credential arrives as a parsed object, not a serialized string.
Example fix
// before
await fetch('/api/auth/signIn', {
method: 'POST',
body: JSON.stringify({ email, password, credential: JSON.stringify(token) }),
});
// after — pass the structured credential object
await fetch('/api/auth/signIn', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ email, password, credential: { provider: 'turnstile', token } }),
}); Defensive patterns
Strategy: validation
Validate before calling
import { z } from 'zod';
const credentialSchema = z.object({
provider: z.enum(['hashcash', 'turnstile']),
token: z.string().min(1),
challenge: z.string().optional(),
});
const parsed = credentialSchema.safeParse(rawCredential);
if (!parsed.success) fixCredentialShape(parsed.error); Type guard
function isCaptchaCredential(v: unknown): v is { provider: 'hashcash' | 'turnstile'; token: string; challenge?: string } {
return (
!!v && typeof v === 'object' &&
['hashcash', 'turnstile'].includes((v as any).provider) &&
typeof (v as any).token === 'string' && (v as any).token.length > 0
);
} Try / catch
try {
captchaService.assertValidCredential(credential);
} catch (e) {
if (e?.code === 'captcha_verification_failed' && e.message === 'Invalid Credential') {
return badRequest('credential malformed — re-run the captcha widget');
}
throw e;
} Prevention
- Mint credentials only through the provided client-side captcha helper/widget.
- Validate the credential shape client-side before submitting protected requests.
- Never stringify-then-nest the credential; send the structured object in JSON.
When it happens
Trigger: Client sends a captcha credential without a provider field, with provider not equal to 'hashcash' or 'turnstile', without a token string, or with a challenge of the wrong type; hand-rolled API clients or scripts posting form data that never went through the captcha widget; JSON payloads where credential is a string instead of an object.
Common situations: Custom automation hitting sign-up/sign-in endpoints without generating a Turnstile token or hashcash stamp; frontend changes that renamed the credential payload keys; proxy/middleware stripping the captcha header before it reaches the service.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Invalid config for module [${module}] with key [${key}] Valu
- invalid_checkout_parameters
- email_token_not_found
- invalid_indexer_input
- ValueNotExists
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/d5b7d56bb8058879.
Report an issue: GitHub.