Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Sell through agents as a SaaS platform


Private preview

Sell through agents as a SaaS platform Private preview

Learn how to let your businesses sell their products through AI chat agents.

Private preview

If your platform wants to use agentic commerce to sell your businesses’ products, join the waitlist. Agentic commerce for platforms is available to businesses in the US and requires waitlist approval.

Use agentic commerce to let your connected accounts sell products through AI chat agents. Your platform uploads product feed data and configures checkout hooks for tax and fees, and connected accounts opt in to agent channels. When a buyer makes a purchase through an agent, Stripe runs checkout and calls your platform’s hooks before completing the transaction.

Set up your platform

Go to the Agentic commerce settings page in the Dashboard to onboard your platform. The onboarding wizard guides you through:

  • Creating a Stripe Profile for your platform.
  • Choosing your charge type ( direct or destination with on_behalf_of ).
  • Enabling AI agent channels your connected accounts can sell through.
  • Configuring seller settings, including your webhook endpoint, receipt type, and support policies.

After you complete onboarding, you can manage these settings from the same page.

Choose your charge type

In the in-context shopping flow, Stripe creates an agentic checkout session for your platform. To create the session, Stripe needs to know which charge type to use. Stripe supports direct charges and destination charges with the on_behalf_of parameter. In both charge types, the connected account acts as the seller.

Configure your charge type during platform onboarding, or update it later from the Agentic commerce settings page in the Dashboard.

Upload your product catalog data to Stripe

Prepare your product catalog

For each connected account, create a CSV file that conforms to the Stripe product catalog specification.

Upload the product catalog data to Stripe

Upload product catalog data for each connected account separately. Use a sandbox to validate parsing, field mappings, and data quality before enabling live updates.

After you set up catalog uploads, follow these best practices to prevent issues with outdated inventory or pricing. For example, agents might continue to report that a product isn’t available (even if it’s back in stock) until they receive an explicit signal that the inventory has been updated. To troubleshoot continued issues, see Handle out-of-stock and price failures.

Keep catalog feeds current

To prevent purchase failures during agentic checkout, refresh each connected account’s catalog data frequently. How frequently depends on how fast the account’s inventory moves, but in most cases, uploading inventory and pricing data every 15 minutes for each connected account is enough. Product data (titles, descriptions, images, and categories) changes less often, so uploading it once per day is usually enough.

Between full uploads, send targeted changes with the incremental inventory and incremental price feeds instead of re-uploading an account’s entire catalog.

Cleanly remove old products

When a connected account removes a product from its inventory, send a clean deletion. In upsert mode, omitting a product from your feed leaves it unchanged in Stripe’s catalog. To explicitly remove it, set delete=true. Otherwise, the product remains visible to shoppers and agents.

Only use replace mode for complete inventory refreshes

Product feed uploads support two processing modes: upsert and replace. In replace mode, any product not included in the uploaded file is permanently deleted from that connected account’s catalog in Stripe. Use upsert for all incremental updates, and replace only for intentional full-catalog product refreshes. See Feed processing mode.

Feed typeFrequencyPurpose
Product dataOnce per dayTitles, descriptions, images, and categories
InventoryEvery 15 minutesPrevents agents from showing out-of-stock items
PricingEvery 15 minutesHelps keep the checkout price aligned with the quoted price
PromotionsAs neededOffers discount codes, deals, and free shipping to drive conversion

Note

Feed uploads are processed as independent, asynchronous tasks. We don’t guarantee processing or completing uploads in the order you submit them. If you upload multiple files in quick succession, a later upload can finish before an earlier one.

For inventory that moves faster than your upload cadence, set up the product price and availability hook so Stripe can confirm current price and stock immediately before a checkout completes.

Common mistake

If you use a restricted API key, it must have Product Catalog Imports write permission. Without this permission, API requests return a 403 error.

Create a ProductCatalogImport for the connected account. A successful request returns a ProductCatalogImport object in the awaiting_upload state. The status_details.awaiting_upload.upload_url.url field in the response contains the presigned URL for your file upload. The maximum upload size is 4GB.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Common mistake

The presigned upload URL expires after 5 minutes. Upload your CSV before the URL expires. If it expires, create a new ProductCatalogImport to get a new upload URL.

Upload your CSV to the presigned URL. The file must be in CSV format, where each row represents one product or variant. The maximum file size is 4 GB.

Command Line

After Stripe receives the file, the import transitions from awaiting_upload to processing.

Monitor feed status

Stripe validates and cleans the product catalog data, indexes it, and converts it to a format that AI agents can use. We recommend listening for webhook events so you’re notified as soon as indexing completes, instead of polling for status. Listen for the v2.commerce.product_catalog.imports.succeeded, v2.commerce.product_catalog.imports.succeeded_with_errors, and v2.commerce.product_catalog.imports.failed terminal webhook events. The webhook includes the import ID in related_object.id, which you can use to retrieve the full object.

If you can’t receive webhook events, poll the import object instead until it reaches a terminal state: succeeded, succeeded_with_errors, or failed.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

If the import completes without errors, it reaches succeeded. If the file is structurally valid but contains row-level validation issues, the import reaches succeeded_with_errors. If the import can’t complete, for example because the file was never uploaded, the file is unreadable, or an internal error occurs, it reaches failed.

If your import status is succeeded_with_errors, you can download the error file:

  1. Find the status _ details. succeeded _ with _ errors. error _ file. url field in the response.
  2. Download the CSV directly from that URL before it expires.
  3. The CSV contains only the rows that failed, with a leading stripe _ error _ message column describing each error.

Note

Error file URLs expire after 5 minutes. To get a new URL, call the retrieve endpoint again.

You can also filter your connected accounts list by Product catalog status to see which accounts have catalogs uploaded.

Configure taxes for your connected accounts

Use Stripe Tax for fully managed tax calculations, or implement a tax hook in your integration if you need custom tax logic or must integrate a third‑party tax provider.

Use Stripe Tax to calculate and collect tax for each of your connected accounts. See the Tax for platforms setup to configure Stripe Tax.

In your product catalog CSV upload, set the stripe_product_tax_code column to associate each product with a tax treatment.

See tax codes for the full list of supported tax codes.

You can also calculate taxes through third-party tax providers, such as Anrok and Avalara.

Monetize transactions

By default, when you create an agentic checkout session, no application fee is applied.

Common mistake

You must add an application fee when your platform pays Stripe fees for a connected account. Without an application fee, you can lose money on each transaction.

Use a checkout customization hook to handle the v1.delegated_checkout.finalize_checkout event and return an application fee.

See webhooks for information about setting up webhooks in your application.

server.js

Select a language

Node.js

Python

Ruby

PHP

Java

Go

.NET

No results

const stripe = require('stripe');
const express = require('express');
const app = express();

app.use("/agentic-commerce-hook", express.raw({ type: "application/json" }));
app.post("/agentic-commerce-hook", async (req, res) => {
 const sig = req.headers["stripe-signature"];
 const endpointSecret = process.env.WEBHOOK_SECRET; // Store this securely

 try {
 stripe.webhooks.signature.verifyHeader(req.body, sig, endpointSecret, stripe.webhooks.DEFAULT_TOLERANCE);
 } catch (err) {
 console.log(`Webhook signature verification failed: ${err.message}`);
 return res.status(400).send(`Webhook Error: ${err.message}`);
 }

 const event = JSON.parse(req.body);
 console.log("Received event:", event.type);
 if (event.type === "v1.delegated_checkout.finalize_checkout") {
 const data = event.data;
 // Perform validation checks for order approval
 const orderAmount = data.amount_total || 0;
 const lineItems = data.line_items_details || [];
 const connectedAccount = event.context;
 const stripeCheckoutSessionId = data.checkout_session;

 const applicatonFee = await calculateApplicationFee(lineItems);
 const {reason, isApproved} = await calculateOrderApproval();

 if (isApproved) {
 return res.status(200).json({
 manual_approval_details: {
 type: "approved"
 },
 application_fee_details: {
 application_fee_amount: applicatonFee
 }
 });
 } else {
 return res.status(200).json({
 manual_approval_details: {
 type: "declined",
 declined: {
 reason,
 }
 }
 });
 }
 }

 return res.status(400).json({ error: "Unsupported webhook type" });
});

app.listen(4567, () => console.log("Server is running on port 4567"));

Enable your connected accounts to sell with agents

Connected accounts must review the agent terms and enable an agent before they can sell through it. Stripe sends the agent an approval request that the agent must accept. Connected accounts can manage which AI agents sell their products, customize how their business appears across agent platforms, and pause or stop selling on any agent.

Embed the agentic commerce settings component directly in your platform’s UI to give connected accounts a native onboarding and management experience. For details on how to embed Dashboard functionality into your website, see Get started with Connect embedded components.

server.rb

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

require 'sinatra'
require 'stripe'
# This is a placeholder - it should be replaced with your secret API key.
# Sign in to see your own test API key embedded in code samples.
# Don't submit any personally identifiable information in requests made with this key.
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')

post '/account_session' do
 content_type 'application/json'

 begin
 account_session = client.v1.account_sessions.create({
 account: '{{CONNECTED_ACCOUNT_ID}}',
 components: {
 agentic_commerce_settings: {
 enabled: true,
 }
 }
 })

 {
 client_secret: account_session[:client_secret]
 }.to_json
 rescue => error
 puts "An error occurred when calling the Stripe API to create an account session: #{error.message}";
 return [500, { error: error.message }.to_json]
 end
end

index.html

Select a language

HTML + JS

React

No results

<head>
 <script type="module" src="index.js" defer></script>
</head>
<body>
 <h1>Payments</h1>
 <div id="container"></div>
 <div id="error" hidden>Something went wrong!</div>
</body>

index.js

Select a language

HTML + JS

React

No results

import {loadConnectAndInitialize} from '@stripe/connect-js';

const fetchClientSecret = async () => {
 // Fetch the AccountSession client secret
 const response = await fetch('/account_session', { method: "POST" });
 if (!response.ok) {
 // Handle errors on the client side here
 const {error} = await response.json();
 console.error('An error occurred: ', error);
 document.querySelector('#error').removeAttribute('hidden');
 return undefined;
 } else {
 const {client_secret: clientSecret} = await response.json();
 document.querySelector('#error').setAttribute('hidden', '');
 return clientSecret;
 }
}

const stripeConnectInstance = loadConnectAndInitialize({
 // This is a placeholder - it should be replaced with your publishable API key.
 // Sign in to see your own test API key embedded in code samples.
 // Don't submit any personally identifiable information in requests made with this key.
 publishableKey: "pk_test_TYooMQauvdEDq54NiTphI7jx",
 fetchClientSecret: fetchClientSecret,
 });
const paymentComponent = stripeConnectInstance.create("agentic-commerce-settings");
const container = document.getElementById("container");
container.appendChild(paymentComponent);

Respond to purchases and fulfill orders

Listen to Stripe webhooks to monitor orders made on AI chat agents.

When an order is confirmed, Stripe emits webhook events that your server can handle to run fulfillment logic. Set up an endpoint on your server to accept, process, and acknowledge these events. See the webhooks guide for step-by-step instructions on integrating with and testing Stripe webhooks.

Stripe emits checkout.session.completed and payment_intent.succeeded. If your fulfillment logic already handles these events, you don’t need additional integration changes. You can customize your fulfillment logic for in-context agentic selling (for example, by noting in your order confirmation email that the checkout occurred through an agent).

For details on setting up webhooks for connected accounts, see Connect webhooks.

server.js

Select a language

Node.js

Python

Ruby

PHP

Java

Go

.NET

No results

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const express = require('express');
const app = express();

// Use the secret provided by Stripe CLI for local testing
// or your webhook endpoint's secret
const endpointSecret = 'whsec_...';

app.post('/webhook', async (request, response) => {
 const sig = request.headers['stripe-signature'];
 let event;

 try {
 event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
 } catch (err) {
 response.status(400).send(`Webhook Error: ${err.message}`);
 return;
 }

 if (event.type === 'checkout.session.completed') {
 let session = event.data.object;
 // For V1 webhooks event.account is the connected account
 session = await stripe.checkout.sessions.retrieve(
 session.id,
 {
 expand: ["line_items.data.price.product", "line_items.data.price"],
 },
 // If using direct charges, the checkout session is created on the connected account and the `stripeAccount` parameter must be passed. If using destination charges the checkout session is created on the platform account and `stripeAccount` parameter can be ignored.
 {
 stripeAccount: "{{CONNECTED_ACCOUNT_ID}}",
 },
 );

 // SKU id is available at session.line_items.data[number].price.external_reference
 fulfillCheckout(event.account, session);
 }

 response.status(200).send();
});

After you receive the webhook, retrieve all required fields with a single API call. To avoid multiple requests, expand sub-resources using the expand request parameter with the preview header Stripe-Version: 2025-12-15.preview.

Command Line

See fields in the expanded Checkout Session, such as amount_total, quantity, and SKU ID.

Checkout session field reference

Test your integration

You can test your integration directly from the Dashboard in a sandbox:

  1. Open the Trigger Agentic Purchase blueprint in Workbench
  2. Enter the connected account’s ID and a SKU ID
  3. Click Run to simulate a charge from an agent

Optional Manual capture

Optional Set up an order approval hook

Optional Set up a checkout customization hook

Optional Set up a product price and availability hook

Optional Test your hooks

Optional Handle the agreement with agents

Optional Send incremental inventory updates

Optional Send incremental price updates

Optional Handle out-of-stock and price failures

Optional Add promotions to drive conversion

Optional Handle refunds and disputes

Optional Export your catalog feed

Last verified 2026-09-24

Is this helpful?