Skip to content

Stripe API errorStripe: “Not a valid URL” (url_invalid)

Creating a Checkout Session, an Account Link or a customer portal session fails with code url_invalid, and on success_url the message often reads “Not a valid URL”. Stripe needs a full URL with the scheme and domain. A path like /success, a host without https://, or a URL built from an environment variable that is unset on the server all fail.

Short answer

Log the exact URL your server sends. Build it from one base URL that includes https:// and the domain, for example https://example.com/success, and make sure that variable is set where the code runs. For webhooks, register a public HTTPS URL, or use the Stripe CLI to forward events to localhost.

Why Stripe rejects the URL, and the fix

  1. 01

    The URL is a relative path

    “The URL to which Stripe should send customers when payment or setup is complete.” Stripe, Create a Checkout Session (success_url)

    success_url and cancel_url are where Stripe sends the customer after Checkout, from Stripe’s own domain. Stripe needs a complete URL to send the customer to; a path like /success has no domain, so Stripe cannot use it. Pass the full URL.

    const base = process.env.APP_URL; // e.g. https://example.com
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items,
      success_url: `${base}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${base}/cart`,
    });
  2. 02

    The base URL variable is unset

    “The URL provided is invalid.” Stripe, Error codes (url_invalid)

    The most common case in deployed apps: the base URL comes from an environment variable that exists locally but not on the server, so Stripe receives undefined/success or null/success. Some frameworks only expose variables with a public prefix to the browser, so a server variable read in the wrong place is empty too. A stray space or newline at the end of the variable, or a doubled https://https://, breaks it the same way. Log the URL just before the call.

  3. 03

    The scheme is missing

    example.com/success or localhost:3000/success has no scheme. Start the URL with https:// in production.

  4. 04

    A mobile deep link or custom scheme

    Developers report that a custom scheme such as myapp://success is rejected as success_url. Send the customer to an https page you control, and open the app from there with a universal link or an app link.

  5. 05

    Embedded Checkout uses return_url

    “This parameter is required if `ui_mode` is `embedded_page` or `elements` and redirect-based payment methods are enabled on the session.” Stripe, Create a Checkout Session (return_url)

    With embedded Checkout, success_url is not allowed. Pass return_url instead, as a full URL.

  6. 06

    Related: the session ID never appears on your success page

    “This is a literal string and you must add it exactly as you see it here.” Stripe, Customize the success page

    This does not cause url_invalid, but it breaks the page after it. Keep {CHECKOUT_SESSION_ID} exactly as written. Do not URL-encode the braces with encodeURIComponent or new URL(), and do not swap in an ID yourself; Stripe fills it in after payment.

  7. 07

    A webhook endpoint on localhost

    “Registered webhook endpoints must be publicly accessible HTTPS URLs.” Stripe, Receive Stripe events in your webhook endpoint

    Stripe rejects a localhost or non-HTTPS webhook endpoint, because it has to reach the endpoint over the internet. Use the Stripe CLI to forward events to your machine while you develop, and register the public HTTPS URL once deployed.

    stripe listen --forward-to localhost:3000/api/webhooks/stripe
  8. 08

    Connect onboarding and portal return URLs

    “The URL that the user will be redirected to upon leaving or completing the linked flow.” Stripe, Create an account link (return_url)

    Account Links need refresh_url and return_url, and the customer portal takes a return_url. They follow the same rule: a full URL with the scheme, from the same base URL variable.

Checked against the vendor’s docs on : Stripe docs: Error codes; Stripe API: Create a Checkout Session; Stripe docs: Customize the success page; Stripe docs: Webhooks; Stripe API: Create an account link.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Stripe “Received unknown parameter” · Stripe webhook signature verification failed · Stripe works in test mode, not live · Stripe “No such PaymentMethod”

Questions

More on this error

Why does it work locally but not after deploy?

Usually the base URL variable is set in your local .env but not on the host, so the deployed server sends undefined/success. Set it in the host’s environment settings and redeploy.

Can success_url point to a mobile app?

Checkout redirects in a browser, so use an https page you control, and send the person into the app from there with a universal link or an app link.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.