Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

5282 articles

Add server-side logic


Add server-side logic

Validate and process user actions and data in your app using backend code.

Use the Accounts v2 API to represent customers

The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.

To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.

For most use cases, we recommend modelling your customers as customer-configured Account objects instead of using Customer objects.

With Stripe Apps, you can add server-side logic with a self-hosted backend. With a self-hosted backend service, you can:

  • Integrate securely with third-party systems that require a server-side integration.
  • Subscribe to webhook events from Stripe and synchronize Stripe with other systems.
  • Use long-lived app logic that executes when the user closes the browser.
  • Build apps that provide cron-job-like functionality to schedule specific actions.

How the self-hosted backend relates to the app

Authenticate users from your UI to your app’s back end

To authenticate a user from the Dashboard, the back end needs a signature with the shared secret and the account and user ID of the current, signed-in Dashboard user. If your user doesn’t have permission to call the API, Stripe returns a Permission error.

Before you begin

  1. Make sure your back-end service can send and receive HTTP requests. If you haven’t built an API server before, consider trying the interactive webhook endpoint builder.
  2. Create your shared secret by uploading your app: Command Line stripe apps upload Don’t worry if you haven’t finished developing the current version of your app, uploading won’t update your app in live mode.
  3. Get your app’s secret to verify the signature in your backend: a. Go to your Stripe app details page by selecting your app from Apps. b. Under the application ID, click the overflow menu (), then click Signing secret to open the signing secret dialog. c. Click the clipboard to copy your app’s secret from the signing secret dialog.

Caution

Run stripe apps upload at least once before using fetchStripeSignature during local development. This generates the signing secret that the function needs to work in preview mode.

Send a signed request

To send a signed request to the app’s backend:

  1. Get the current signature using the fetchStripeSignature asynchronous function.
  2. Add the signature to the Stripe-Signature header.
  3. Include the user _ id and account _ id objects in the request.
  4. On the app’s backend, verify that the request includes the signature, app secret, user _ id , and account _ id .

See an example of sending a signed request with additional data.

An example request from a Stripe app with the Stripe-Signature header:

Sample backend verifying the request:

Please be aware that the order and naming of the payload fields matters when performing signature verification. The user_id precedes the account_id and the resulting object is as follows: { user_id, account_id }

// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')(process.env.STRIPE_API_KEY);
const express = require('express');

// Find your app's secret in your app settings page in the Developers Dashboard.
const appSecret = 'absec_...';

// This example uses Express.
const app = require('express')();
app.use(express.json());

// Match the raw body to content type application/json.
app.post('/do_secret_stuff', (request, response) => {
 const sig = request.headers['stripe-signature'];
 // Retrieve user id and account id from the request body
 const payload = JSON.stringify({
 user_id: request.body['user_id'],
 account_id: request.body['account_id']
 });
 try {
 // Verify the payload and signature from the request with the app secret.
 stripe.webhooks.signature.verifyHeader(payload, sig, appSecret);
 } catch (error) {
 response.status(400).send(error.message);
 }
 // Handle the request by returning a response
 // to acknowledge receipt of the event.
 response.json({ success: true });
});

app.listen(3000, () => console.log('Running on port 3000'));

Send a signed request with additional data

You can authenticate a user by sending a signed request with a payload (additional data). When you call the fetchStripeSignature function with an additional payload request, you create a signature with user_id, account_id and the additional payload you passed into the function. By default, Stripe apps use user_id and account_id to generate the signature string.

An example of generating a secret with additional payload:

// A valid payload object has keys of type string
// and values of type string, number, or boolean.
const payload = {
 "transaction_id": 'ipi_1KRmFUFRwUQjTSJEjRnCCPyV',
 "amount": 100,
 "livemode": false,
};

(async () => {
 fetch(`https://example.com/do_more_secret_stuff/`, {
 method: 'POST',
 headers: {
 'Stripe-Signature': await fetchStripeSignature(payload),
 'Content-Type': 'application/json',
 },
 // Append the account ID and user ID in the body to verify on backend.
 body: JSON.stringify({
 ...payload,
 user_id: 'usr_K6yd2CbXLO9A5G',
 account_id: 'acct_1JSkf6FRwUQjTSJE',
 }),
 });
})();

Sample backend verifying the signature generated with additional payload:

// Match the raw body to content type application/json.
app.post('/do_more_secret_stuff', (request, response) => {
 try {
 // Verify the signature from the header and the request body that
 // contains the additional data, user ID, and account ID with the app secret.
 stripe.webhooks.signature.verifyHeader(request.body, sig, appSecret);
 } catch (error) {
 response.status(400).send(error.message);
 }
 // Handle the request by returning a response
 // to acknowledge receipt of the event.
 response.json({ success: true });
});

Verify user roles (optional)

You can verify the user roles assigned to a given user_id by including the stripe_roles key in the payload. Provide this with userContext?.roles, which returns a list of RoleDefinitions. If any role in the payload isn’t assigned to the user_id provided, fetchStripeSignature returns an invalid request error (400).

// Provide this special key in the same way you'd
// provide any other key to the additional payload.
const payload = {
 "stripe_roles": userContext?.roles,
};

fetch(`https://example.com/do_more_secret_stuff/`, {
 method: 'POST',
 headers: {
 'Stripe-Signature': await fetchStripeSignature(payload),
 'Content-Type': 'application/json',
 },
 // Append the account ID and user ID in the body to verify on backend.
 body: JSON.stringify({
 ...payload,
 user_id: 'usr_K6yd2CbXLO9A5G',
 account_id: 'acct_1JSkf6FRwUQjTSJE',
 }),
});

Expire and create secrets

If your secret is compromised, you can expire your current app’s secret immediately for up to 24 hours to update the app’s secret on your backend. During this time, two secrets are active for the endpoint, the compromised secret and the newly generated secret. Stripe generates one signature per secret until expiration.

To expire and create an app secret:

  1. Go to your Stripe app details page by selecting your app from Apps .
  2. On the page header, click the overflow menu ( ), then click Signing secret to open the Signing secret dialog.
  3. Click Expire secret from the signing secret dialog to open the Expire secret dialog.
  4. Select an expiration duration for your current’s app secret.
  5. Click Expire secret .

Handle Cross-Origin Resource Sharing (CORS)

Cross-Origin Resource Sharing (CORS) is an important part of helping keep apps secure from cross-site scripting attacks (XSS). Because Stripe App UI extensions are, by necessity, cross-origin and sandboxed iframe, you must employ a specific approach to handling cross-origin request headers.

For your UI extension to retrieve data from your backend service, you must configure your backend service to do the following:

  • Allow requests using the Options method .
  • To allow requests from null origins, set the Access-Control-Allow-Origin to * .

Note

UI extensions have a null origin because they run within a secure sandboxed iframe for security purposes.

Many backend frameworks have libraries and guidance to help you handle CORS. Check the documentation for your framework for more specific guidance.

To authenticate that a request came from Stripe on behalf of a particular user or account, see Authenticate users from your UI to your back end.

Caution

Only configure authenticated endpoints and any endpoints the UI extension communicates with to use Access-Control-Allow-Origin: *. Unauthenticated endpoints are vulnerable to the related setting attacks if no other measures are in place.

Use Stripe APIs

To interact with Stripe, you can use and authenticate your requests to the Stripe API.

Authenticating requests

To authenticate your requests, use your existing merchant account API key to interact with Stripe and specify the user’s stripeAccountId.

For server-side API calls, you can make requests as connected accounts using the special header Stripe-Account with the Stripe account identifier (it starts with the prefix acct_) of your platform user. Here’s an example that shows how to Create a PaymentIntent with your platform’s API secret key and your user’s Account identifier.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

The Stripe-Account header approach is implied in any API request that includes the Stripe account ID in the URL. Here’s an example that shows how to Retrieve an account with your user’s Account identifier in the URL.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

In addition, all of Stripe’s server-side libraries support this approach on a per-request basis, as shown in the following example:

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

client = Stripe::StripeClient.new("{{PLATFORM_SECRET_KEY}}")
client.v1.customers.create(
 {email: 'person@example.com'},
 {stripe_account: '{{CONNECTED_STRIPE_ACCOUNT_ID}}'}
)

# Fetching an account just needs the ID as a parameter
client.v1.accounts.retrieve('{{CONNECTED_STRIPE_ACCOUNT_ID}}')

Call your self-hosted backend from your UI extension

When you make requests from your UI extension to your back end, send a signature with your request to validate the legitimacy of the requests. From the UI extension, pass the stripeAccountId for the current user so that you can make back-end requests on behalf of that user.

// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
// Find your keys at https://dashboard.stripe.com/apikeys.
const stripe = require('stripe')('sk_test_Ou1w6LVt3zmVipDVJsvMeQsc');
const express = require("express");
const app = express();

app.use(express.static("public"));
app.use(express.json());

app.post("/api/data", async (req, res) => {
 const { stripeAccountId } = req.body;

 const customer = await stripe.customers.create({
 description: 'My First Test Customer (created for API docs)',
 }, {
 stripeAccount: stripeAccountId,
 });

 res.send({
 data: []
 });
});

app.listen(3000, () => console.log("Node server listening on port 3000!"));

Call other APIs

From your self-hosted backend, you can call any API—your own API or one built by another developer or company.

For more information, learn how to store secret credentials and tokens in your app.

If you need to pass user information from Stripe to another service, use the stripeAccountId passed from your UI extension.

You can also call a third-party API from your UI extension.

Receive event notifications about your app

Listen for events (such as user installs or uninstalls) on your Stripe app using incoming webhooks so your integration can automatically trigger reactions in your backend such as:

  • Creating user accounts
  • Updating permissions
  • Disabling a user’s account and removing data

Receive events

You can receive events from Stripe for an app that’s private to your account only or an app that’s listed on the App Marketplace:

To receive events for an app that’s listed publicly on the App Marketplace:

  1. Handle webhook events in your app’s back end .
  2. Register a webhook endpoint in the Stripe Dashboard, and select Listen to events on Connected accounts when registering your webhook endpoint.
  3. Add the event _ read permission to your app: Command Line stripe apps grant permission "event_read" "Allows reading event data from users who have installed the app"
  4. For each event that your webhook endpoint is listening to, add the corresponding permission: Command Line stripe apps grant permission "the related setting" "the related setting" Replace:
  • the related setting _ the related setting with the permission name for an event .
  • the related setting with an explanation for enabling access. Users see this explanation when they install your app. For example: “Allows reading event data from users who have installed the app.”

When a business triggers an event, Stripe provides the following Event object. This event includes the account property specifying the account ID of the business who triggers the event:

{
 "id": "evt_4vcr2GCWpBy871",
 "livemode": true,
 "object": "event",
 "type": "app.install.created",
 "account": "acct_7wkuDEj1xkGzUo",
 "pending_webhooks": 2,
 "created": 1349654313,
 "data": {...}
}

Using the account attribute, you can do the following:

  • Monitor how many businesses install and uninstall your app.
  • Make API calls on behalf of users with Stripe Connect .

Events for Stripe Apps

In addition to the types of events Stripe supports, Stripe Apps also supports the following events:

Merchant actionResulting webhook event sent to the app’s backend
Connect or install your appaccount.application.authorized
Disconnect or uninstall your appaccount.application.deauthorized

To learn more about listening for install events for your app and what data is returned, see Listen to events.

Event behaviour depends on install mode

Your users can install apps in live mode, test mode, both modes, or a sandbox environment. Set webhooks according to the following guidelines:

  • If the app is installed in a sandbox environment, events are only sent to the sandbox environment.
  • If the app is installed in live mode only, live mode events are sent to the live mode endpoint.
  • If the app is installed in test mode only, test mode events are sent to the test mode endpoint.
  • If the app is installed in both modes, test mode events are sent to both the test mode and live mode endpoints, and live mode events are sent to the live mode endpoint.

Configure the Connect /webhook for live and test modes, then use the following snippet for both modes of the app. See the webhooks doc for a full endpoint example.

Select a language

Ruby

Python

Java

Node.js

Go

.NET

No results

require 'sinatra'
require 'json'

post '/webhook' do
 event = JSON.parse(request.body.read)

 if event['livemode']
 puts "Handling live event: #{event}"
 # Handle live events
 handle_live_event(event)
 else
 puts "Handling test event: #{event}"
 # Handle test events
 handle_test_event(event)
 end

 status 200
 body 'Event received'
end

Troubleshooting

If you don’t receive expected events, review your configuration for the following common oversights:

  • Make sure live mode webhooks use live mode keys and test mode webhooks use test mode keys.
  • For live mode events, make sure the installing account is activated.
  • Make sure that your app can handle both live mode & test mode events.
  • Triggering test events doesn’t replicate live event behaviour unless explicitly set up in the app configuration.

Test webhooks locally

You can test webhooks locally for:

  • An app that’s only available to all users on your account and listens to events on your own account
  • An app that’s available on the Stripe App Marketplace and listens to events on accounts that have installed your app

To test webhooks locally:

  1. Install the Stripe CLI.
  2. Authenticate your account: Command Line stripe login
  3. Open two terminal windows:

For more information, see our docs on testing a webhook endpoint.

See also

Last verified 2026-09-25

Is this helpful?