calcom/cal.diy · critical · InternalServerErrorException

Failed generating a Stripe checkout session URL.

Error message

Failed generating a Stripe checkout session URL.

What it means

Thrown by StripeService.generateTeamCheckoutSession after stripe.checkout.sessions.create returns a session whose url is null/empty. Because a checkout session without a URL is unusable, the service throws 500 InternalServerError. This is a server-side failure, not a client input error - the cause is in the Stripe configuration or the session parameters.

Source

Thrown at apps/api/v2/src/modules/stripe/stripe.service.ts:208

          quantity: 1,
        },
      ],
      customer_update: {
        address: "auto",
      },
      // Disabled when testing locally as usually developer doesn't setup Tax in Stripe Test mode
      automatic_tax: {
        enabled: this.environment === "production",
      },
      metadata: {
        pendingPaymentTeamId,
        ownerId,
        dubCustomerId: ownerId, // pass the userId during checkout creation for sales conversion tracking: https://d.to/conversions/stripe
      },
    });

    if (!session.url) {
      throw new InternalServerErrorException({
        message: "Failed generating a Stripe checkout session URL.",
      });
    }

    return session;
  }

  async getStripeCustomerIdFromUserId(userId: number) {
    const user = await this.usersRepository.findById(userId);

    if (!user?.email) return null;
    const customerId = await this.getStripeCustomerId(user);
    if (!customerId) {
      return this.createStripeCustomerId(user);
    }

    return customerId;
  }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Verify teamMonthlyPriceId is set to an active, non-archived Stripe price in the current environment.
  2. Confirm in the Stripe dashboard that Checkout sessions can be created on the connected account.
  3. Retry after fixing config; if it persists, capture the full Stripe session response in logs to see why url is null.
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the configured team price exists in Stripe before checkout.
async function teamPriceIsLive(stripe, priceId: string) {
  const price = await stripe.prices.retrieve(priceId);
  return price.active;
}

Type guard

function hasCheckoutUrl(session: { url?: string | null }): session is { url: string } {
  return typeof session.url === 'string' && session.url.length > 0;
}

Try / catch

try {
  const session = await stripeService.generateTeamCheckoutSession(teamId, ownerId);
  return session;
} catch (e) {
  if (e.status === 500 && /checkout session URL/.test(e.message ?? '')) {
    // alert ops: teamMonthlyPriceId likely invalid/archived; verify config
    throw new OperationalError('Stripe checkout config issue');
  }
  throw e;
}

Prevention

When it happens

Trigger: Creating a team checkout session where Stripe returns a session object but no url - typically because teamMonthlyPriceId is invalid/missing, the price is archived, the Stripe account cannot create sessions, or a region/currency restriction applies.

Common situations: The STRIPE_TEAM_MONTHLY_PRICE_ID (teamMonthlyPriceId) config points to a deleted/archived Stripe price. The Stripe account is restricted from creating Checkout sessions. A Stripe-side incident. The webhook/checkout feature is disabled on the account.

Related errors


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