Sell through agents as a SaaS platformPrivate 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_ 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 type | Frequency | Purpose |
|---|---|---|
| Product data | Once per day | Titles, descriptions, images, and categories |
| Inventory | Every 15 minutes | Prevents agents from showing out-of-stock items |
| Pricing | Every 15 minutes | Helps keep the checkout price aligned with the quoted price |
| Promotions | As needed | Offers 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 Import 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_ state. The status_ field in the response contains the presigned URL for your file upload. The maximum upload size is 4GB.
curl -X POST https://api.stripe.com/v2/commerce/product_catalog/imports \ -H "Authorization: Bearer sk_test_Hrs6SAopgFPF0bZXSN3f6ELN" \ -H "Stripe-Version: 2026-07-29.preview" \ -H "Stripe-Account:" \ --json '{ "feed_type": "product", "mode": "upsert", "metadata": { "file_name": "march_11_2026_product_upload.csv" } }'{{CONNECTED_ACCOUNT_ID}}
{ "id": "pcimprt_xxx", "object": "v2.commerce.product_catalog_import", "created": "2026-03-26T00:35:01.000Z", "feed_type": "product", "status": "awaiting_upload", "status_details": { "awaiting_upload": { "upload_url": { "expires_at": "2026-03-26T00:40:02.000Z", "url": "https://stripeusercontent.com/files/us-west-2/upload/wksp_xxx" } } }, "livemode": true }
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.
curl -X PUT \ -H "Content-Type: text/csv" \ --data-binary @"/path/to/your/file.csv" \ "{{PRESIGNED_URL}}"
After Stripe receives the file, the import transitions from awaiting_ 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., v2., and v2. terminal webhook events. The webhook includes the import ID in related_, 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_, or failed.
curl https://api.stripe.com/v2/commerce/product_catalog/imports/\ -H "Authorization: Bearer sk_test_Hrs6SAopgFPF0bZXSN3f6ELN" \ -H "Stripe-Version: 2026-07-29.preview" \ -H "Stripe-Account:{{PRODUCT_CATALOG_IMPORT_ID}}"{{CONNECTED_ACCOUNT_ID}}
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_. 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_, you can download the error file:
- Find the
status_field in the response.details. succeeded_ with_ errors. error_ file. url - Download the CSV directly from that URL before it expires.
- The CSV contains only the rows that failed, with a leading
stripe_column describing each error.error_ message
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_ 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. event and return an application fee.
See webhooks for information about setting up webhooks in your application.
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.
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_Hrs6SAopgFPF0bZXSN3f6ELN') 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
<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>
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_A7jK4iCYHL045qgjjfzAfPxu", 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. and payment_. 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.
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..
curl https://api.stripe.com/v1/checkout/sessions/{{SESSION_ID}}?expand[]=line_items.data.price.product&expand[]=line_items.data.taxes&expand[]=payment_intent.latest_charge \ -u sk_test_Hrs6SAopgFPF0bZXSN3f6ELN: \ -H "Stripe-Version: 2025-12-15.preview" \ -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"
See fields in the expanded Checkout Session, such as amount_, quantity, and SKU ID.
Checkout session field reference
Test your integration
You can test your integration directly from the Dashboard in a sandbox:
- Open the Trigger Agentic Purchase blueprint in Workbench
- Enter the connected account’s ID and a SKU ID
- Click Run to simulate a charge from an agent