Skip to main content
Before your users can connect their Stripe accounts by OAuth, Stripe needs a Stripe App that you own and they install. There’s no form to fill in. A Stripe App is a small project you generate with the Stripe CLI, describe in a manifest and upload. It takes about ten minutes, most of them installing the CLI. Only connecting your own account? A secret or restricted key is all the stripe pack needs: Set up Stripe. Users who would rather paste a restricted key need no app either: Stripe OAuth.
The test takes two Stripe accounts. Upload the app from one whose business is verified: a sandbox, or an account that hasn’t submitted its business details, gets no test links. Then install it from a different account. Stripe issues a token for the account that owns the app, and refuses it on every call.
Every Stripe link below opens in a new tab, so this page stays where you left it.
1

Install the CLI

Stripe Apps also needs Node.js 22 or later and pnpm 10. stripe login pairs the CLI with one account. Pick the verified account that will own the app.The first command that touches an app asks the account to accept the Stripe Apps Agreement, and prints the Dashboard link to do it.
2

Generate the app

The display name is what your users see when they install. The ID can’t be changed after the first upload, and it’s unique across all of Stripe, sandboxes included, so an upload anywhere claims it.The generated project is a Dashboard extension with a page of its own. An app that only connects accounts needs no page, so the last two lines remove it.
3

Declare OAuth and the permissions

Each permission is a line on the consent screen, with its purpose beside it, and the token can do nothing else. A tool that needs one the app lacks fails with a 403 coded more_permissions_required_for_application. Grant what the pack’s tools need, from Stripe’s permissions reference:Prices are plan_read and plan_write, not price_*.No command sets the redirect URLs. Add them to stripe-app.yaml, under declarations:
stripe-app.yaml
The first URL is the default. Live mode redirects only to https. A test-mode install may come back to http://localhost, which is how a script on your machine catches the code. Remove it before you submit the app for review: Stripe’s publishing guide lists a localhost or placeholder redirect URL among the common reasons an app is turned back.
4

Upload it

Without --force, the CLI stops at a check that calls an app with no page “no functionality yet”. Here the permissions are the function.Upload from the account’s test mode. If the CLI answers “You’re in live mode”, stripe switch context moves it out.
5

Turn on external testing

In the Dashboard, open Apps overview, then Created, and click the app. Under External testing, click Get started, keep the version you just uploaded, and click Set version.The page now has a Test OAuth section with three links: Live mode link, Test mode link and Sandbox link. Clicking one copies it. Copy the Test mode link:
The Install links row higher up has the same three names. Those are the public links. They stay inactive until the app is published, and they are the ones App Review asks for.
6

Point the flow at the test link

The link’s client_id is the app’s client ID. Its path, up to authorize, is the consent endpoint until you publish. After that it’s the one in STRIPE_APPS, the constant on Stripe’s provider page.
stripe_test_link.py
The secret key’s mode follows the link’s. A test-mode install is exchanged with the owner’s sk_test_ key, and a live one with its sk_live_ key.
7

Install it from another account

Open the link flow.authorize() built. Stripe says the app is a test version and that installing it into test mode won’t affect the live account. Click Continue, choose an account other than the one that owns the app, and approve the permissions.The callback receives code and state, and Stripe adds stripe_user_id: the account that installed. Exchange the code as on Getting the grant. The access token begins rk_test_, and the refresh token rolls on every use. From here the per-user client is the one in the Stripe provider page.scripts/live_oauth_check.py runs this step from a terminal against your app, then checks the result: the pack’s read tools, a refresh, and two workers taking turns on one grant.

Publish it

Until Stripe approves the app, only the accounts that open its External test link can connect, 25 at most. Publishing opens it to every Stripe account, through the endpoint STRIPE_APPS already declares. Before approval that public link answers “The provided OAuth link is invalid”. Stripe’s publishing guide has the full list. For an app like this one it comes to:
  • An activated account. Stripe needs your business details first, and publishes one app per account.
  • Your real callback. Replace the localhost redirect with your https one, raise version in stripe-app.yaml, upload, and set that version as the one to publish.
  • An icon. icon in stripe-app.yaml, the same square image as the listing’s logo, at least 300 by 300 pixels.
  • The listing. A name of 35 characters or fewer that avoids “Stripe”, “app”, “Generator”, “free” and “paid”; a subtitle; up to three feature images that show no real customer data; pricing; a privacy policy; and one or two support channels.
  • What the reviewers need. Testing steps, and credentials for test accounts they can sign in to without two-factor authentication. The install URL you submit must use the public link from the app’s Settings tab, not the External test link.
Stripe answers within about four business days. A later change to the app or its listing is reviewed again. Once it’s published, drop STRIPE_APPS_TEST and build the flow on STRIPE_APPS as declared. A live-mode install is exchanged with the owner’s sk_live_ key. To check the published link end to end, with the address your browser lands on pasted back in:

When it does not work

The app was uploaded from a sandbox, or from an account that hasn’t submitted its business details. Its app page shows no test links. Upload it again from a verified account, under a new ID.
The CLI is logged into a sandbox. Setting the flag lets the upload through, but the sandbox still asks for business verification before it shows test links. Log the CLI into the verified account instead.
App IDs are unique across Stripe, and an upload from any account, a sandbox included, claims one. Change id in stripe-app.yaml and upload again.
The Install button on the app’s own page installs it on that account without the consent flow, so nothing is redirected. Install from the test link, from another account.
The message ends “Application access may have been revoked”, but the app is still installed. It was installed on the account that owns it. Install it from another account.
The tool needs a permission the manifest doesn’t grant. Grant it, raise version in stripe-app.yaml, upload, and set the new version under External testing.
The refresh token was used and its replacement was never saved. Stripe rolls refresh tokens on every use, so the old one stops working at once. Store the new one in on_refresh, then have the user connect again.
  • Stripe OAuth — the server constant, the per-user client, and a pasted restricted key beside OAuth
  • Getting the grant — the two routes, state and PKCE, inside your app
  • Stripe — the pack, form encoding, idempotency and Connect