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

  1. 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.
  2. As an API caller: request a fresh verification code and retry within the 15-minute TOTP window.
  3. Verify CALENDSO_ENCRYPTION_KEY is identical on the service that issued the code and the service verifying it.
  4. 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

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


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/bdc505e85ee8ba85. Report an issue: GitHub.