Server side integration
The backend integration with Stripe involves using your secret API key to make requests.
Authentication
As the platform account, when looking at connected account details, platform account balance, or platform reports or balance transactions, use your secret key to authenticate API requests.
Here’s an example platform account API call:
curl https://api.stripe.com/v1/balance \ -u
:sk_test_4eC39HqLyjWDarjtT1zdp7dc
As the connected account, use the account ID of the connected account (acct_***
) along with the Stripe-Account header when retrieving the connected account balance, direct charging, creating customers and payment methods, and so on. Although it’s possible to authenticate as the connected account using their API keys, storing these keys is bad practice and presents additional security risks if they were to be compromised. By contrast, account IDs are only usable by authenticated platforms and their secret keys.
Here’s an example API call on behalf of a connected account:
curl https://api.stripe.com/v1/balance \ -u
: \ -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}"sk_test_4eC39HqLyjWDarjtT1zdp7dc
API requests best practices
Stripe recommends adding an idempotency key to all POST requests. The key should be unique, such as a universally unique identifier (UUID) or a combination of customer ID and order ID for example. These keys allow you to safely retry requests if you experience a network error.
Customer objects: storing payment details
To store and reuse PaymentMethods, you must attach them to Customer objects.
We recommend saving Customer objects and PaymentMethods on the connected accounts.
Here’s an example creating a Customer on behalf of a connected account:
curl https://api.stripe.com/v1/customers \ -u
: \ -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \ -X "POST" \ -d "name"="Jenny Rosen" \ -d "email"="jenny.rosen@stripe.com" \ -d "payment_method"="{PAYMENT_METHOD_ID}"sk_test_4eC39HqLyjWDarjtT1zdp7dc
To reuse payment details (for example, one-click checkout) across different connected accounts, you must create Customer objects and PaymentMethod objects on the platform account.
Cloning payment methods
If you’re migrating an existing platform to Standard Connect, it might be easier to migrate all Customers and PaymentMethods to the platform account. You should migrate Customer objects that will be cloned to your connected accounts, to your platform accounts. By tokenizing the Customer object while authenticated as the connected account, the token can be used directly on any account connected to the platform. This process involves (1) creating a Customer object on the platform and (2) using the PaymentMethods endpoint to retokenize that PaymentMethod for use on the connected account using the Stripe-Account header. The cloning payment methods documentation contains more info about this approach.
Authorize payments and capture funds later
Stripe supports separating authorization and capture of funds on the customer’s credit card. The issuing bank of the card guarantees the capture of reserved funds for a set number of days after the authorization (7 by default). If the funds aren’t captured within the seven-day window, the authorization is automatically released and a new authorization request is required to charge the customer’s card. Capturing reserved funds requires a second API call to Capture a PaymentIntent.
Payments with direct charges
To accept a payment, authenticate as the connected account and create Direct Charges. When creating Direct Charges, you have the option to take an application fee on each charge. Charges contribute to the connected account’s pending balance minus any application fees or Stripe processing fees. After the available_on
date passes, the pending balance becomes available. The available balance is paid out to connected bank accounts based on their payout schedule.
Here’s an example direct charge created with the Payment Intents API:
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \ -X "POST" \ -d "application_fee_amount"=123 \ -d "amount"=1099 \ -d "currency"="usd" \ -d "customer"="{CUSTOMER_ID}" \ -d "payment_method_types[]"="card" \ -d "payment_method"="{PAYMENT_METHOD_ID}" \ -d "confirm"="true"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Refunds for direct charges
Refunds are issued on behalf of the connected account. When creating a refund, Stripe won’t refund the application fee to the connected account by default. If you want to return any application fees to the connected account, you can pass the refund_application_fee value as true
when creating the refund.
Here’s an example refund for a direct charge:
curl https://api.stripe.com/v1/refunds \ -u
: \ -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \ -X "POST" \ -d "payment_intent"="{PAYMENT_INTENT_ID}" \ -d "refund_application_fee"="true"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Payments with PaymentIntents
The PaymentIntents API is Stripe’s API for building dynamic payment flows. It tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods. PaymentIntents are ideal for international payments because they’re SCA-ready and support different authentication methods.
Refer to the PaymentIntents Quickstart guide to learn more.
While PaymentIntents track the state for a payment, underlying Charge objects represent actual money movement and conversations with card networks. We recommend saving both the PaymentIntent (pi_***
) and Charge (ch_***
) IDs for easy reference. A PaymentIntent might have multiple Charges (some Charges representing failed payments) with a maximum of one successful Charge.
Immediate capture payments
To accept a payment, you need to authorize and capture a charge. Charges contribute to your account’s pending balance minus Stripe’s processing fees. On the available_on
date, the pending balance becomes available to be paid out to your bank account based on your payout schedule.
Here’s an example of creating and confirming a PaymentIntent with an existing PaymentMethod:
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -X "POST" \ -d "amount"=1099 \ -d "currency"="usd" \ -d "customer"="{CUSTOMER_ID}" \ -d "payment_method_types[]"="card" \ -d "payment_method"="{PAYMENT_METHOD_ID}" \ -d "confirm"="true"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Authorize and capture funds later
Stripe supports separating authorization and capture of funds on the customer’s credit card. The issuing bank of the card guarantees the capture of reserved funds for a set number of days after the authorization (7 by default). If the funds aren’t captured within the seven-day window, the authorization is automatically released and a new authorization request is required to charge the customer’s card.
To indicate that you want to separate authorization and capture, you must set the value of the capture_method
parameter to manual
when creating the PaymentIntent. This instructs Stripe to only authorize and not yet capture the amount on the customer’s card.
Here’s an authorization using PaymentIntents:
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -X "POST" \ -d "amount"=1099 \ -d "currency"="usd" \ -d "payment_method_types[]"="card" \ -d "capture_method"="manual"sk_test_4eC39HqLyjWDarjtT1zdp7dc
To capture the authorized funds, you need to make a PaymentIntent capture request. The total authorized amount is captured by default. To capture less than the authorized amount (for example, 7.50 USD of the 10.99 USD authorization), pass the amount_to_capture
parameter. A partial capture automatically releases the remaining amount. You can’t use amount_to_capture
to capture more than the original authorization amount. This capture request, while very likely to succeed, is not guaranteed.
Here’s a capture using PaymentIntents:
curl https://api.stripe.com/v1/payment_intents/{PAYMENT_INTENT_ID}/capture \ -u
: \ -X "POST"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Refunds
Refunds are managed using the Refunds API and can be made for full or partial amounts. To refund a transaction with Stripe, you’ll need either the PaymentIntent ID or the Charge ID for the transaction you need to refund.
Refunds use your available Stripe balance, and can’t use your pending balance. If your available balance doesn’t have sufficient funds to cover the amount of the refund, Stripe debits the remaining amount from your bank account. You can issue partial refunds, full refunds, and more than one refund against a charge, but you can’t refund a total greater than the original charge amount.
You can issue refunds using the API or the Dashboard. You can’t cancel a refund after you issue it. It will take 5 - 10 business days for the refund to appear on the customer’s statement. If a customer is curious about the status of their refund, you can provide the ARN so that they can inquire about the refund with their bank.
Here’s an example refund for a PaymentIntent:
curl https://api.stripe.com/v1/refunds \ -u
: \ -X "POST" \ -d "payment_intent"="{PAYMENT_INTENT_ID}"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Here’s a partial refund example with an amount specified:
curl https://api.stripe.com/v1/refunds \ -u
: \ -X "POST" \ -d "payment_intent"="{PAYMENT_INTENT_ID}" \ -d "amount"=1000sk_test_4eC39HqLyjWDarjtT1zdp7dc
Configuring webhooks
You can use webhooks to capture events that occur on your account (like payouts to your bank account, refunds, payments, etc). They’re helpful when handling Stripe events that occur asynchronously, or for those that you want to trigger additional actions for.
WEBHOOK TYPE | RECOMMENDED WEBHOOKS |
---|---|
CHARGES |
|
REFUNDS |
|
PAYOUTS |
|
PAYMENT INTENTS |
|
DISPUTES |
|
In addition to these webhooks, for Standard Connect you should configure both “Account” and “Connect” webhooks. Account webhooks are used to capture events that occur on the platform account (like payouts to the platform’s bank account), whereas Connect webhooks capture events on the connected account (account updates, payments, refunds, payouts to the connected account’s bank account, etc).
Connect webhooks contain an account property to identify the connected account by ID.
While only test webhooks get sent to your development webhook URLs, both live and test webhooks get sent to your production webhook URLs. This is so you can perform both live and test transactions under a production application. For this reason, we recommend you check the livemode property when receiving an event webhook to know what action you should take.
Important Connect webhook events
- account.application.authorized—This event occurs when a connected account connects to your platform. This is important so your platform can perform any required setup.
- account.application.deauthorized—This event occurs when a connected account disconnects from your platform. This is important so your platform can perform any required cleanup processes.
See also
These resources will help you set up your webhooks and validate that they’ve been configured correctly.