calcom/cal.diy · error · BadRequestException

error=${error}&error_description=${error_description}

Error message

error=${error}&error_description=${error_description}

What it means

Thrown by StripeController.save when the Stripe OAuth callback carries an `error` query parameter that is not the benign 'access_denied' (user-cancelled) value. The controller stringifies { error, error_description } and throws it as 400 BadRequest, so the response body contains the raw OAuth error details from Stripe.

Source

Thrown at apps/api/v2/src/modules/stripe/controllers/stripe.controller.ts:132

          const response = await this.httpService.axiosRef.get(url, { params, headers });
          const redirectUrl = response.data?.url || decodedCallbackState.onErrorReturnTo || "";
          return { url: redirectUrl };
        } catch (err) {
          const fallbackUrl = decodedCallbackState.onErrorReturnTo || "";
          return { url: fallbackUrl };
        }
      }

      // user-level fallback
      const userId = await this.tokensRepository.getAccessTokenOwnerId(decodedCallbackState.accessToken);

      // user cancels flow
      if (error === "access_denied") {
        return { url: getOnErrorReturnToValueFromQueryState(state) };
      }

      if (error) {
        throw new BadRequestException(stringify({ error, error_description }));
      }

      if (!userId) {
        throw new BadRequestException("Invalid Access token.");
      }

      return await this.stripeService.saveStripeAccount(decodedCallbackState, code, userId);
    } catch (error) {
      if (error instanceof Error) {
        console.error(error.message);
      }
      return {
        url: decodedCallbackState.onErrorReturnTo ?? "",
      };
    }
  }

  @Get("/check")

View on GitHub (pinned to 176037d0af)

Solutions

  1. Read error_description from the response body to get Stripe's explanation, then restart the flow from /v2/stripe/redirect.
  2. Verify the redirect URI registered in the Stripe dashboard exactly matches api.url + '/v2/stripe/save'.
  3. Confirm the Stripe app is active and the connecting account is in good standing in the Stripe dashboard.
Defensive patterns

Strategy: try-catch

Validate before calling

// The callback is server-side; clients handle the resulting redirect URL.
// Prevent by validating redirect URI + app config before starting the flow:
async function preflightStripe(api) {
  const status = await api.getStripeStatus();
  if (!status.appConfigured) throw new Error('Stripe app not configured');
}

Try / catch

// Server-side controller already stringifies the OAuth error.
// On the client, inspect the returned url / error payload:
if (result.url && /error=/.test(result.url)) {
  surfaceToUser('Stripe authorization failed; please reconnect.');
}

Prevention

When it happens

Trigger: Stripe redirects back to /v2/stripe/save?error=invalid_request&error_description=... (or invalid_grant, redirect_uri_mismatch, etc.) because something went wrong during authorization, and the value is not 'access_denied'.

Common situations: The authorization code was reused or expired. The redirect URI in the request does not match the one registered in the Stripe app. The Stripe app was deleted or restricted. The user's Stripe account lacks permission. A Stripe platform outage returned an error.

Related errors


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