Using OAuth with Express accounts

Use the OAuth connection flow to allow an Express user to connect to your platform.

The OAuth connection flow

A user connects to your platform using the following OAuth connection flow:

  1. From a page on your site, the user clicks a link that redirects them to Stripe, passing along your platform’s client_id.
  2. On Stripe’s website, the user provides the necessary information for connecting to your platform.
  3. The user is redirected to your site, along with an authorization code.
  4. Your site then makes a request to Stripe’s OAuth token endpoint to complete the connection and fetch the user’s account ID.

After these steps are complete, you can make API requests for the user with their account ID.

To start your integration, go to your platform settings and:

  • Enable onboarding Express accounts with OAuth in the OAuth settings.
  • Copy your client_id, a unique identifier for your platform that’s generated by Stripe.
  • Set your redirect_uri, the URL that your user is redirected to after connecting their account. You must specify all redirect URLs in your platform settings. If you don’t include the redirect_uri parameter in your request, Stripe defaults to using the first address you’ve configured in your platform settings.

Stripe also provides a development client_id to make testing easier.

With these two pieces of information in hand, you’re ready to create the OAuth link. We recommend showing a Connect button that sends users to our Express OAuth endpoint:

https://connect.stripe.com/express/oauth/authorize?redirect_uri=https://connect.stripe.com/connect/default/oauth/test&client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}

To prevent CSRF attacks, add the state parameter with the value set to a unique token. Stripe includes this state value in the redirect URL that sends the user back to your site.

Here’s how you can present the example URL, along with our Connect with Stripe button:

Connect with Stripe

Customize Express with OAuth parameters

You can change the behavior of the Express onboarding flow by including additional URL parameters in your OAuth link. A complete list of available parameters is available in the OAuth reference.

Individual or company accounts

You can specify whether Stripe should present an Express onboarding form for individuals or companies by setting the stripe_user[business_type] parameter to either individual or company.

Stripe collects the right information for each type of account. For example, to onboard a company:

https://connect.stripe.com/express/oauth/authorize?redirect_uri=https://connect.stripe.com/connect/default/oauth/test&client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}&stripe_user[business_type]=company

Prefill form fields

You can prefill some form fields on the user’s Stripe application by including the relevant URL parameters in your OAuth link.

This example prefills the user’s email address with stripe_user[email]:

https://connect.stripe.com/express/oauth/authorize?redirect_uri=https://connect.stripe.com/connect/default/oauth/test&client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}&stripe_user[email]=user@example.com

Specify capabilities for an account

You can add or change the capabilities that are applied to your new connected accounts in the dashboard settings for Express. However, if you want to request different capabilities for each of your connected accounts, you can include the suggested_capabilities[] parameter in your OAuth link and override the dashboard settings on the Express Configuration page.

An example with the transfers capability:

https://connect.stripe.com/express/oauth/authorize?redirect_uri=https://connect.stripe.com/connect/default/oauth/test&client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}&suggested_capabilities[]=transfers

Stripe adds the suggested capability to this Express account, unless one of the following conditions is met:

  • If the user is in a country that doesn’t support transfers, Stripe attempts to designate the account as card_payments.
  • If transfers and card_payments are both not supported for the user, the connected account is marked as having no capabilities.

You can verify that Stripe added the suggested capability by using the assert_capabilities[] parameter. This step is optional.

Step 2: User creates their account

After the user clicks the link on your site, they’re taken to Stripe’s website where they’re prompted to provide contact and payout information.

To test the onboarding process, you can provide (000) 000-0000 as a phone number. Instead of sending you an SMS message or email, Stripe lets you complete verification with the code 000-000.

Express displays your branding in the onboarding flow and the Express dashboard. You can provide your platform name, logo, and optional brand color in the Connect settings section of the Stripe Dashboard.

Step 3: User is redirected back to your site

After the user completes the onboarding process, they’re redirected back to your site using the URL defined as your platform’s redirect_uri.

For successful connections, the following values are passed back in the redirect URL:

  • The state value, if provided.
  • An authorization code. The authorization code is short-lived, and can be used only once, in the POST request described in the next step.
https://connect.stripe.com/connect/default/oauth/test?code={AUTHORIZATION_CODE}

Step 4: Platform completes the Express account connection

Include the provided authorization code in a POST request to Stripe’s token endpoint to complete the connection and fetch the user’s account ID:

curl https://connect.stripe.com/oauth/token \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d code=ac_123456789 \ -d grant_type=authorization_code
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' response = Stripe::OAuth.token({ grant_type: 'authorization_code', code: 'ac_123456789', }) # Access the connected account id in the response connected_account_id = response.stripe_user_id
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' response = stripe.OAuth.token( grant_type='authorization_code', code='ac_123456789', ) # Access the connected account id in the response connected_account_id = response['stripe_user_id']
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $response = \Stripe\OAuth::token([ 'grant_type' => 'authorization_code', 'code' => 'ac_123456789', ]); // Access the connected account id in the response $connected_account_id = $response->stripe_user_id;
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; Map<String, Object> params = new HashMap<>(); params.put("grant_type", "authorization_code"); params.put("code", "ac_123456789"); TokenResponse response = OAuth.token(params, null); // Access the connected account ID in the response String accountId = response.getStripeUserId();
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const response = await stripe.oauth.token({ grant_type: 'authorization_code', code: 'ac_123456789', }); var connected_account_id = response.stripe_user_id;
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" params := &stripe.OAuthTokenParams{ GrantType: stripe.String("authorization_code"), Code: stripe.String("ac_123456789"), } token, _ := oauth.New(params) // Access the connected account id in the response connected_account_id = token.StripeUserID
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var options = new OAuthTokenCreateOptions { GrantType = "authorization_code", Code = "ac_123456789", }; var service = new OAuthTokenService(); var response = service.Create(options); // Access the connected account id in the response connected_account_id = response.StripeUserId

Stripe returns a response that includes the account ID (stripe_user_id) for the user:

{ "access_token": "{ACCESS_TOKEN}", "livemode": false, "refresh_token": "{REFRESH_TOKEN}", "token_type": "bearer", "stripe_publishable_key": "{PUBLISHABLE_KEY}", "stripe_user_id": "{ACCOUNT_ID}", "scope": "express" }

If there was a problem, a detailed error is returned:

{ "error": "invalid_grant", "error_description": "Authorization code does not exist: {AUTHORIZATION_CODE}" }

The user is now connected to your platform. The stripe_user_id is the Stripe account ID for the new account. Store this value in your database and use it to authenticate as the connected account by passing it into requests in the Stripe-Account header.

Use the refresh_token to generate test access tokens for a production client_id or to roll your access token.

Verify the account’s capability

If you provide the suggested_capabilities[] parameter, you can add the assert_capabilities[] parameter to verify that the suggested capability was applied to the connected account. This step is optional, however. Stripe handles any failure to apply a capability silently. You should check this if you’re concerned about URL security.

curl https://connect.stripe.com/oauth/token \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d code=ac_123456789 \ -d grant_type=authorization_code \ -d "assert_capabilities[]"=transfers
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' response = Stripe::OAuth.token({ grant_type: 'authorization_code', code: 'ac_123456789', assert_capabilities: ['transfers'], }) # Access the connected account id in the response connected_account_id = response.stripe_user_id
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $response = \Stripe\OAuth::token([ 'grant_type' => 'authorization_code', 'code' => 'ac_123456789', 'assert_capabilities' => ['transfers'], ]); // Access the connected account id in the response $connected_account_id = $response->stripe_user_id;
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' response = stripe.OAuth.token( grant_type='authorization_code', code='ac_123456789', assert_capabilities=['transfers'], ) # Access the connected account id in the response connected_account_id = response.stripe_user_id
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const response = await stripe.oauth.token({ grant_type: 'authorization_code', code: 'ac_123456789', assert_capabilities: ['transfers'], }); const connected_account_id = response.stripe_user_id;
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; ArrayList<String> capabilities = new ArrayList<>(); capabilities.add("transfers"); Map<String, Object> params = new HashMap<>(); params.put("grant_type", "authorization_code"); params.put("code", "ac_123456789"); params.put("assert_capabilities", capabilities); TokenResponse response = OAuth.token(params, null); // Access the connected account id in the response String accountId = response.getStripeUserId();
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" params := &stripe.OAuthTokenParams{ GrantType: stripe.String("authorization_code"), Code: stripe.String("ac_123456789"), AssertCapabilities: stripe.StringSlice([]string{ "transfers", }), } o, _ := oauth.New(params) // Access the connected account id in the response // o.StripeUserID
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var options = new OAuthTokenCreateOptions { GrantType = "authorization_code", Code = "ac_123456789", AssertCapabilities = new List<string> { "transfers", }, }; var service = new OAuthTokenService(); var token = service.Create(options); // Access the connected account ID in the response // token.StripeUserId

A success response then looks like:

{ "access_token": "{ACCESS_TOKEN}", "livemode": false, "refresh_token": "{REFRESH_TOKEN}", "token_type": "bearer", "stripe_publishable_key": "{PUBLISHABLE_KEY}", "stripe_user_id": "{ACCOUNT_ID}", "scope": "express", "capabilities": "transfers" }

If the specified capabilities[] value doesn’t match, the error response looks like:

{ "error": "invalid_request", "error_description": "assert_capabilities expects capability: card_payments" }

The most common reason for this request failure is that the specified capability is not available in your user’s country. A less likely reason is a bad actor changing the URL.

Webhooks

After an account is created, all account change notifications can be delivered to your webhooks as account.updated events. You set your Connect webhook URL in your account settings. These events let you track connected account onboarding and verification status, which can be useful for providing user support and displaying relevant notices in your platform’s user interface. This isn’t necessary, though, because Stripe takes your users through the steps of the onboarding and verification process and handles any issues that arise. Your platform doesn’t need to do any additional work.

If you created an account in test mode using either a test mode API key or client_id, Stripe doesn’t send any emails while you build your Stripe integration (but we do when you’re live).