calcom/cal.diy · error · BadRequestException
Invalid verification code
Error message
Invalid verification code
What it means
Thrown by VerificationAtomService.verifyEmailCodeUnAuthenticated when the underlying verifyCodeUnAuthenticated rejects with an Error whose message equals 'invalid_code'. It is meant to map an upstream 'wrong/expired TOTP code' failure into a 400 Bad Request with a user-friendly message. The TOTP code is derived from md5(email + CALENDSO_ENCRYPTION_KEY) with a 900s step, so any code older than ~15 min or computed with a different key will fail. IMPORTANT: under the current source this branch is effectively DEAD — verifyCodeUnAuthenticated throws new Error('Invalid verification code') (not 'invalid_code'), so the message match never succeeds and the catch falls through to the generic 'Verification failed' (error 22).
Source
Thrown at apps/api/v2/src/modules/atoms/services/verification-atom.service.ts:34
@Injectable()
export class VerificationAtomsService {
constructor(
private readonly atomsSecondaryEmailsRepository: AtomsSecondaryEmailsRepository,
private readonly usersRepository: UsersRepository
) {}
async checkEmailVerificationRequired(input: CheckEmailVerificationRequiredParams) {
return await checkEmailVerificationRequired(input);
}
async verifyEmailCodeUnAuthenticated(input: VerifyEmailCodeInput) {
try {
return await verifyCodeUnAuthenticated(input.email, input.code);
} catch (error) {
if (error instanceof Error) {
if (error.message === "invalid_code") {
throw new BadRequestException("Invalid verification code");
}
if (error.message === "BAD_REQUEST") {
throw new BadRequestException("Email and code are required");
}
}
throw new BadRequestException("Verification failed");
}
}
async verifyEmailCodeAuthenticated(user: UserWithProfile, input: VerifyEmailCodeInput) {
try {
return await verifyCodeAuthenticated({
user,
email: input.email,
code: input.code,
});
} catch (error) {
if (error instanceof Error) {View on GitHub (pinned to 176037d0af)
Solutions
- If you are a platform maintainer: align the catch predicate with the real upstream message — compare against 'Invalid verification code' (or better, throw a typed ErrorWithCode from verifyCodeUnAuthenticated and check with instanceof) so this branch actually fires.
- As an API caller: request a fresh verification code and retry within the 15-minute TOTP window.
- Verify CALENDSO_ENCRYPTION_KEY is identical on the service that issued the code and the service verifying it.
- Inspect the actual upstream error message in a debugger to confirm the mismatch before patching the predicate.
Example fix
// before
if (error.message === "invalid_code") {
throw new BadRequestException("Invalid verification code");
}
// after — match the real upstream message (or use a typed error)
if (error.message === "Invalid verification code" || error.message === "invalid_code") {
throw new BadRequestException("Invalid verification code");
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate before calling verifyEmailCodeUnAuthenticated
const emailOk = typeof input.email === 'string' && input.email.includes('@') && input.email.length > 3;
const codeOk = typeof input.code === 'string' && /^\d{6}$/.test(input.code);
if (!emailOk || !codeOk) throw new BadRequestException('Email and code are required'); Type guard
function isVerifyEmailCodeInput(v: unknown): v is { email: string; code: string } {
return typeof v === 'object' && v !== null &&
typeof (v as any).email === 'string' &&
typeof (v as any).code === 'string';
} Try / catch
try {
await service.verifyEmailCodeUnAuthenticated(input);
} catch (e) {
// NOTE: today this is the generic 'Verification failed' (error 22), not this message.
if (e instanceof BadRequestException && e.message === 'Invalid verification code') {
// prompt user to re-enter code
}
throw e;
} Prevention
- Request a fresh code and submit within the 15-minute TOTP window.
- Keep CALENDSO_ENCRYPTION_KEY identical between code issuer and verifier.
- Validate the 6-digit shape before hitting the endpoint to avoid wasted attempts and rate-limit tripping.
When it happens
Trigger: POST to the atoms verify-email-code-unauthenticated endpoint with an email/code pair where the upstream library threw an Error literally equal to 'invalid_code'. Because the upstream never emits that exact string today, this only triggers if the upstream contract is changed to throw 'invalid_code', or if a rate-limit/other error happens to be renamed.
Common situations: Developer enters a wrong 6-digit verification code; developer enters an expired code (older than the 900s TOTP step); CALENDSO_ENCRYPTION_KEY differs between code-generation and verification environments; upstream library version change that alters thrown error message strings.
Related errors
- Email and code are required
- Verification failed
- Email, code, and user ID are required
- Email already exists
- ${err.message}
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/bdc505e85ee8ba85.
Report an issue: GitHub.