calcom/cal.diy · error · BadRequestException
Missing `state` query param
Error message
Missing `state` query param
What it means
Thrown by ConferencingController.save (the OAuth callback handler at GET /v2/conferencing/{app}/oauth/callback) when the `state` query parameter is missing. Cal.com encodes the entire OAuth flow context (accessToken, returnTo, teamId, orgId) into the state param before redirecting to Zoom/Microsoft; the provider echoes it back. Without state, the callback cannot reconstruct who initiated the flow. Returns HTTP 400.
Source
Thrown at apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts:148
@Get("/:app/oauth/callback")
@UseGuards()
@Redirect(undefined, 301)
@ApiOperation({ summary: "Conferencing app OAuth callback" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [ZOOM, OFFICE_365_VIDEO],
required: true,
})
async save(
@Query("state") state: string,
@Param("app") app: string,
@Query("code") code: string,
@Query("error") error: string | undefined,
@Query("error_description") error_description: string | undefined
): Promise<{ url: string }> {
if (!state) {
throw new BadRequestException("Missing `state` query param");
}
const decodedCallbackState: OAuthCallbackState = JSON.parse(state);
try {
if (error) {
throw new BadRequestException(error_description);
}
if (decodedCallbackState.teamId && decodedCallbackState.orgId) {
const apiUrl = this.config.get("api.url");
const url = `${apiUrl}/organizations/${decodedCallbackState.orgId}/teams/${decodedCallbackState.teamId}/conferencing/${app}/oauth/callback`;
const params: Record<string, string | undefined> = { state, code, error, error_description };
const headers = {
Authorization: `Bearer ${decodedCallbackState.accessToken}`,
};
try {
const response = await this.httpService.axiosRef.get(url, { params, headers });
const redirectUrl = response.data?.url || decodedCallbackState.onErrorReturnTo || "";View on GitHub (pinned to 176037d0af)
Solutions
- Ensure the auth-url endpoint (GET /v2/conferencing/{app}/oauth/auth-url) is used to start the flow so state is generated and embedded by Cal.com.
- Verify the redirect URI registered in the Zoom/Microsoft app console matches exactly and the provider is configured to return state.
- Do not call the callback endpoint directly; always enter through the auth-url redirect.
- If testing locally, append a valid base64/JSON-encoded state generated from a real access token.
Example fix
// before: manual redirect construction
window.location.href = `${apiUrl}/v2/conferencing/zoom/oauth/callback?code=${code}`;
// after: obtain the auth url first, then redirect
const { data } = await api.getConferencingOauthUrl('zoom');
window.location.href = data.authUrl; // includes state, returnTo, onErrorReturnTo Defensive patterns
Strategy: validation
Validate before calling
function hasStateParam(query: URLSearchParams): asserts query is URLSearchParams & { get(k: 'state'): string } {
if (!query.get('state')) {
throw new Error('OAuth callback missing state param — start the flow via /oauth/auth-url.');
}
}
hasStateParam(new URL(req.url).searchParams); Type guard
function hasValidStateQuery(q: URLSearchParams): q is URLSearchParams {
return Boolean(q.get('state'));
} Prevention
- Never construct the callback URL manually; always redirect through the auth-url endpoint so state is generated server-side.
- Verify the redirect URI registered in Zoom/Microsoft exactly matches and preserves query params.
- Educate users not to bookmark the callback URL.
- Reject callback requests with no state at the edge (WAF/middleware) to reduce noise.
When it happens
Trigger: The OAuth provider (Zoom/Microsoft) redirected back without the state query param; the callback URL was hit directly by a browser/script without state; a misconfigured redirect URI in the provider console stripped state; the user bookmarked/refreshed the callback URL.
Common situations: Redirect URL configured in Zoom/Microsoft Entra missing the state passthrough; manual testing of the callback endpoint; URL shortener or proxy that drops query params; provider deviated from RFC 6749 state handling.
Related errors
- {error_description}
- Invalid conferencing app, available apps are:
- Invalid conferencing app. Available apps: GOOGLE_MEET.
- ${responseBody.error}
- Invalid Access token.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/3b9d6262c6fd7c9a.
Report an issue: GitHub.