calcom/cal.diy · error · Error

Invalid credentials for userId ${credential.userId} and appI

Error message

Invalid credentials for userId ${credential.userId} and appId ${credential.appId}: ${parsedKey.error}

What it means

Constructor of `CloseComCRMService` parses `credential.key` against `credentialSchema` (zod). If the key shape is wrong, it throws an Error whose message interpolates `credential.userId`, `credential.appId`, and the zod error. This stops CRM operations before they can send malformed auth to Close.

Source

Thrown at packages/app-store/closecom/lib/CrmService.ts:72

 *
 * Contact creation
 * Every contact in Close.com need to belong to a Lead. When creating a contact in
 * Close.com as part of this integration, a new generic Lead will be created in order
 * to assign every contact created by this process, and it is named "From Cal.diy"
 */
class CloseComCRMService implements CRM {
  private integrationName = "";
  private closeCom: CloseCom;
  private log: typeof logger;

  constructor(credential: CredentialPayload) {
    this.integrationName = "closecom_other_calendar";
    this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });

    const parsedKey = credentialSchema.safeParse(credential.key);

    if (!parsedKey.success) {
      throw new Error(
        `Invalid credentials for userId ${credential.userId} and appId ${credential.appId}: ${parsedKey.error}`
      );
    }

    // Initialize CloseCom client based on credential type
    if (parsedKey.data.encrypted) {
      // API key authentication
      const decrypted = symmetricDecrypt(parsedKey.data.encrypted, CALENDSO_ENCRYPTION_KEY);
      const { api_key } = JSON.parse(decrypted);
      this.closeCom = new CloseCom(api_key);
    } else if (parsedKey.data.access_token) {
      // OAuth authentication
      this.closeCom = new CloseCom(parsedKey.data.access_token, {
        refresh_token: parsedKey.data.refresh_token,
        expires_at: parsedKey.data.expires_at,
        isOAuth: true,
        userId: credential.userId!,
      });

View on GitHub (pinned to 176037d0af)

Solutions

  1. Read the interpolated `parsedKey.error` in the thrown message — it lists the exact failing field paths.
  2. Have the user reconnect Close.com via OAuth so a fresh, schema-compliant credential is written.
  3. If a schema change caused it, write a migration to backfill/rename fields on existing `closecom_crm` credentials.
  4. Add a setup-time validation step so partial credentials are never persisted.
Defensive patterns

Strategy: validation

Validate before calling

const parsed = credentialSchema.safeParse(credential.key);
if (!parsed.success) {
  const issues = parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; ");
  throw new Error(`Close.com credential invalid: ${issues}`);
}

Type guard

function isCloseCredentialKey(k: unknown) {
  return credentialSchema.safeParse(k).success;
}

Try / catch

try {
  new CloseComCRMService(credential);
} catch (e) {
  if (e instanceof Error && /Invalid credentials/.test(e.message)) {
    await flagCredentialForReconnection(credential.id);
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: A Close.com credential row whose `key` is missing required fields (e.g. no `access_token`/`refresh_token`/`expires_at` for OAuth, or no `encrypted` for API-key mode), has extra/renamed fields, or values of the wrong type — typically after an incomplete OAuth save or a schema migration.

Common situations: OAuth callback persisted a partial credential (token exchange succeeded but `key` written incompletely); schema changed without backfill; manual DB edit; older credential format from a previous app version.

Understand the failure class

Related errors


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