diff --git a/src/lib/validations/whoop.ts b/src/lib/validations/whoop.ts index ea33fe786..f547569d7 100644 --- a/src/lib/validations/whoop.ts +++ b/src/lib/validations/whoop.ts @@ -7,8 +7,10 @@ import { z } from "zod/v4"; * multi-operator product). Stored encrypted on `User`. */ export const whoopCredentialsSchema = z.object({ - clientId: z.string().min(1).max(200), - clientSecret: z.string().min(1).max(200), + // Trimmed: a trailing space or newline from the portal's copy button + // reaches WHOOP verbatim and answers as "unknown client". + clientId: z.string().trim().min(1).max(200), + clientSecret: z.string().trim().min(1).max(200), }); export type WhoopCredentialsInput = z.infer; diff --git a/src/lib/whoop/client.ts b/src/lib/whoop/client.ts index 7eb91358e..90cf76fd2 100644 --- a/src/lib/whoop/client.ts +++ b/src/lib/whoop/client.ts @@ -34,8 +34,10 @@ export interface WhoopCredentials { } function getRedirectUri(): string { + // `||`, not `??`: the compose whitelist materialises the var as an empty + // string when unset, which must still fall through to the derived URI. return ( - process.env.WHOOP_REDIRECT_URI ?? + process.env.WHOOP_REDIRECT_URI || `${process.env.NEXT_PUBLIC_APP_URL}/api/whoop/callback` ); }