Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Overview
Versioning
Changelog
Upgrade your API version
Upgrade your SDK version
Developer tools
SDKs
API
Testing
Workbench
Event Destinations
Workflows
Stripe CLI
Stripe Shell
Developers Dashboard
Agent toolkit
Stripe health alertsBuild with LLMsStripe for Visual Studio CodeFile uploads
Security
Security
Extend Stripe
Stripe Apps
    Overview
    Get started
    Create an app
    How Stripe Apps work
    Sample apps
    Build an app
    Store secrets
    API authentication methods
    Authorization flows
    Server-side logic
    Listen to events
    Handle different modes
    Enable sandbox support
    App settings page
    Build a UI
    Onboarding
    Distribute your app
    Distribution options
    Upload your app
    Versions and releases
    Test your app
    Publish your app
    Promote your app
    Add deep links
    Create install links
    Assign roles in UI extensions
    Post-install actions
    App analytics
    Embedded components for Apps
    Embed third-party Stripe Apps
    Migrating to Stripe Apps
    Migrate or build an extension
    Migrate a plugin to Stripe Apps or Stripe Connect
    Reference
    App manifest
    CLI
    Extension SDK
    Permissions
    Viewports
    Design patterns
    Components
Stripe Connectors
Partners
Partner ecosystem
Partner certification
HomeDeveloper toolsStripe Apps

Add server-side logic

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

Copy page

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.
App backend flowchart

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 backend 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 backend 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.

Send a signed request

Sending 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:

import {fetchStripeSignature} from '@stripe/ui-extension-sdk/utils'; const App = ({ userContext, environment }: ExtensionContextValue) => { const makeRequestToMyBackend = async (endpoint, requestData) => { // By default the signature is signed with user id and account id. const signaturePayload = { user_id: userContext?.id, account_id: userContext?.account.id, }; return fetch(`https://example.com/${endpoint}/`, { method: 'POST', headers: { 'Stripe-Signature': await fetchStripeSignature(), 'Content-Type': 'application/json', }, // Include the account ID and user ID in the body to verify on backend. body: JSON.stringify({ ...requestData, ...signaturePayload, }), }); }; ... }

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, }; 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 backend.

Caution

Only configure authenticated endpoints and any endpoints the UI extension communicates with to use Access-Control-Allow-Origin: *. Unauthenticated endpoints are vulnerable to CSRF 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
cURL
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -H "Stripe-Account:
{{CONNECTED_ACCOUNT_ID}}
"
\ -d amount=1000 \ -d currency=usd \ -d "payment_method_types[]"=card

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
cURL
curl https://api.stripe.com/v1/accounts/
{{CONNECTED_ACCOUNT_ID}}
\ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"

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

Ruby
Stripe.api_key = "{{PLATFORM_SECRET_KEY}}" Stripe::Customer.create( {email: 'person@example.edu'}, {stripe_account: '{{CONNECTED_STRIPE_ACCOUNT_ID}}'} ) # Fetching an account just needs the ID as a parameter Stripe::Account.retrieve('{{CONNECTED_STRIPE_ACCOUNT_ID}}')

Call your self-hosted backend from your UI extension

When you make requests from your UI extension to your backend, 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 backend requests on behalf of that user.

// 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')(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
); 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.

const express = require('express'); const fetch = require('isomorphic-fetch'); const app = express(); app.use(express.static('public')); app.use(express.json()); app.get('/api/time', async (req, res) => { fetch('http://worldclockapi.com/api/json/est/now') .then((response) => response.json()) .then((data) => { res.send({ data: data, }); }); }); app.listen(3000, () => console.log('Node server listening on port 3000!'));

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 backend.
  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 "PERMISSION_NAME" "EXPLANATION"
    Replace:
    • PERMISSION_NAME with the permission name for an event.
    • EXPLANATION 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 merchant triggers an event, Stripe provides the following Event object. This event includes the account property specifying the account ID of the merchant who triggers the event:

{ "id": "evt_vCJ6JtWjps02pB", "livemode": true, "object": "event", "type": "account.application.authorized", "account": "acct_WCLYZOzYSljIWc", "pending_webhooks": 2, "created": 1349654313, "data": {...} }

Using the account attribute, you can do the following:

  • Monitor how many merchants 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

Event behavior 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.

Ruby
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 behavior 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:

    • In one terminal window, Set up event forwarding:

      Command Line
      stripe listen --forward-to localhost:{{PORT}}/webhook
    • In the other terminal window, Trigger events to test your webhooks integration:

      Command Line
      stripe trigger {{EVENT_NAME}}

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

See also

  • Build a UI
  • Upload and install your app
  • Publish your app
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc