# Enable in-context selling on AI agents Learn how to start selling your products in-context using AI agents. > If your business wants to use agentic commerce to sell products, [join the waitlist](https://go.stripe.global/agentic-commerce-contact-sales). > Use this guide if you run an e-commerce business. If you operate a marketplace and want to offer in-context agentic selling on your platform, see our [guide for platforms and marketplaces](https://docs.stripe.com/connect/saas/tasks/enable-in-context-selling-on-ai-agents.md). ## Set up your Stripe account If you don’t already have a Stripe account, [create one](https://stripe.com/register). After you verify your email, activate payments by providing business and personal information, linking a bank account for payouts, and setting up two-step authentication. Then, go to [Agentic Commerce Onboarding](https://dashboard.stripe.com/agentic-commerce) in the Stripe Dashboard. This guides you through creating a [Stripe Profile](https://docs.stripe.com/get-started/account/profile.md) and configuring settings for agentic commerce. ## Configure taxes Use [Stripe Tax](https://docs.stripe.com/tax.md) to manage taxes and apply configurations to individual products. Follow the [tax setup guide](https://docs.stripe.com/tax/set-up.md?dashboard-or-api=api) to configure Stripe Tax. In your product catalog CSV upload, set the `stripe_product_tax_code` column to associate a product with a tax treatment. Stripe also supports tax calculations by third-party tax providers, including Anrok, Avalara, and Sphere. ## Configure support and legal policies From [Agentic Commerce Settings](https://dashboard.stripe.com/settings/agentic-commerce) page, add links to your **Refund and return policy**, **Terms of service**, and **Privacy policy**. You can optionally provide a link to your **Store policy**. ## Upload your product catalog data to Stripe Create a CSV file that conforms to the [Stripe product catalog spec](https://docs.stripe.com/agentic-commerce/product-catalog.md). ### Upload the catalog data to Stripe Send the feed in the Stripe Dashboard or through the Stripe API. You can upload feeds at whatever interval you choose. To keep data current, upload product data once per day and send more frequent incremental updates for inventory and pricing. > Feed uploads are processed as independent, asynchronous tasks. We don’t guarantee uploads are processed or completed in the order you submit them. If you upload multiple files in quick succession, a later upload can finish before an earlier one. #### API Use Stripe APIs to upload your product catalog CSV. We recommend using the sandbox to validate parsing, field mappings, and data quality before enabling live updates. First, upload your product catalog CSV using the [Files API](https://docs.stripe.com/api/files.md). A successful request returns a [File object](https://docs.stripe.com/api/files/object.md), which includes the `id`. - Specify the `data_management_manual_upload` purpose. - The MIME type of the file upload must match its file format. Acceptable formats include CSV and TSV, where each row represents one product or variant. - The maximum file size is 200 MB. ```curl curl https://files.stripe.com/v1/files \ -u <>: \ -F purpose=data_management_manual_upload \ -F file="@/path/to/your/file.csv;type=text/csv" ``` Then, use the Data Management API to create an `ImportSet`. This call starts catalog processing and makes the data available in the Dashboard. Include the following: - The file `id` that the Files API returned. - The preview header (for example, `Stripe-Version: 2025-09-30.clover;udap_beta=v1`). ```curl curl https://api.stripe.com/v1/data_management/import_sets \ -H "Stripe-Version: 2025-09-30.clover;udap_beta=v1" \ -u <>: \ -d file={{FILE_ID}} \ --data-urlencode standard_data_format="product_catalog_feed" ``` ### Monitor feed status and resolve errors Stripe processes your catalog data, validates and cleans it, then indexes it in a format you can send to AI agents. You can monitor indexing progress in two ways: #### API Make a GET request and check the `status` field on the `ImportSet` object. The status can be `pending`, `failed`, `succeeded`, `succeeded_with_errors`, `pending_archive`, or `archived`. ```curl curl https://api.stripe.com/v1/data_management/import_sets/{{IMPORT_SET_ID}} \ -H "Stripe-Version: 2025-09-30.clover;udap_beta=v1" \ -u <>: ``` The response includes the status and any errors: ```json { "id": "impset_7MabcdZ8b617780e5145413", "object": "data_management.import_set", "created": 1643992696, "livemode": true, "result": { "errors": { "file": "file_234923sIENc", "row_count": 30 }, "rows_processed": 120, "successes": { "row_count": 90 } }, "status": "succeeded_with_errors" } ``` If your import status is `succeeded_with_errors`, you can download the error report. 1. Look for the `result.errors.file` field in the response. 1. Use the [Files API](https://docs.stripe.com/api/files.md) to retrieve the content of the error file using the file ID. 1. The downloaded CSV file contains your original data with a leading column named `stripe_error_message` that describes why each row failed. ```curl curl https://files.stripe.com/v1/files/{{ERROR_FILE_ID}}/contents \ -u <>: ``` The API returns a CSV file containing only the rows that failed, with a `stripe_error_message` column describing each error. #### Webhooks Stripe sends `data_management.import_set.succeeded` and `data_management.import_set.failed` events after catalog data indexing completes. The webhook includes the `ImportSet` object. Set up an endpoint to listen for these events. See the [webhooks guide](https://docs.stripe.com/webhooks.md) for step-by-step instructions. Here’s an example of the `data_management.import_set.succeeded` webhook payload: ```javascript { "id": "evt_1SnVHnJ7cqJy0Q5brV8sp35q", "object": "event", "api_version": "2025-01-27.acacia", "created": 1767925127, "data": { "object": { "id": "impset_test_61Twt7AHmQQUohHQq41J7cqJy0Q5bQs4", "object": "data_management.import_set", "created": 1767924632381, "file": "file_1SnV9UJ7cqJy0Q5bpJBbU5Sk", "livemode": false, "status": "succeeded_with_errors", "result": { "errors": { "file": "file_234923sIENc", "row_count": 30 }, "rows_processed": 120, "successes": { "row_count": 90 } } } }, "type": "data_management.import_set.succeeded" } ``` If your import status is `succeeded_with_errors`, you can download the error report. 1. Look for the `result.errors.file` field in the payload. 1. Use the [Files API](https://docs.stripe.com/api/files.md) to retrieve the content of the error file using the file ID. 1. The downloaded CSV file contains your original data with a leading column named `stripe_error_message` that describes why each row failed. ```curl curl https://files.stripe.com/v1/files/{{ERROR_FILE_ID}}/contents \ -u <>: ``` The API returns a CSV file containing only the rows that failed, with a `stripe_error_message` column describing each error. #### Dashboard Go to the [External catalog](https://dashboard.stripe.com/agentic-commerce/catalog) page in the Dashboard and upload your product catalog CSV. ## Respond to purchases and fulfill orders Monitor orders placed through AI chat agents in three ways. #### Dashboard View orders on the [Transactions page](https://dashboard.stripe.com/payments) in the Dashboard—they’re tagged with the originating agent. You can also filter transactions by agent names. #### Webhooks Stripe sends `checkout.session.completed` after the agent completes an order. Each order generates a unique `checkout.session.completed` event. The webhook includes the [CheckoutSession](https://docs.stripe.com/api/checkout/sessions/object.md) object. Set up an endpoint to listen for `checkout.session.completed` events. See the [webhooks guide](https://docs.stripe.com/webhooks.md) for step-by-step instructions. ```javascript const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); // Use the secret provided by Stripe CLI for local testing // or your webhook endpoint's secret const endpointSecret = 'whsec_...'; app.post('/webhook', (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') { const session = event.data.object; // Fulfill the order using the session data fulfillCheckout(session.id); } response.status(200).send(); }); ``` Here’s an example of the `checkout.session.completed` webhook payload: ```javascript { "id": "evt_1SUz6YRhxngcl2jFHhAi1Wiu", "object": "event", "api_version": "2025-10-29.clover", "created": 1763511518, "data": { "object": { "id": "cs_test_a1exHOZ77Pg40P1hPtcWe2oT2xI8G9ruoQohXq6jkKldIPQaGsNSPQmOGZ", "object": "checkout.session", ... "total_details": { "amount_discount": 0, "amount_shipping": 0, "amount_tax": 0 } } }, "livemode": false, ... "type": "checkout.session.completed" } ``` After you receive the webhook, retrieve all required fields with a single API call. To avoid multiple requests, expand sub-resources using the [expand](https://docs.stripe.com/api/expanding_objects.md) request parameter with the preview header `Stripe-Version: 2025-12-15.preview`. ```curl 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 <>: \ -H "Stripe-Version: 2025-12-15.preview" ``` See fields in the expanded [CheckoutSession](https://docs.stripe.com/api/checkout/sessions/object.md), such as `amount_total`, quantity, and SKU ID. ### Checkout session field reference | Order field | Available resource | API path | | ----------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------- | | **Order date** | `CheckoutSession.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.created` | | **Order quantity** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].quantity` | | **SKU** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].price.external_reference` | | **Product description** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].price.product.description` | | **Unit price** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].price.unit_amount` | | **Tax amount** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].Taxes[].amount` | | **Tax type** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].Taxes[].Rate.tax_type` | | **Tax rate** | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].Taxes[].Rate.percentage` | | **ShippingAddress** | `CheckoutSessions` | `CheckoutSessions.CollectedInformation.shipping_details` | | **BillingAddress** | `CheckoutSessions.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.billing_details` | | **Last4** | `CheckoutSessions.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.payment_method_details.card.last4` | | **ExpMonth** | `CheckoutSessions.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.payment_method_details.card.exp_month` | | **ExpYear** | `CheckoutSessions.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.payment_method_details.card.exp_year` | | **CreditCardType** | `CheckoutSessions.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.payment_method_details.card.brand` | | **Final amount** | `CheckoutSessions` | `CheckoutSessions.amount_total` | | **GTIN** (Private preview) | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].price.product.identifiers.gtin` | | **MPN** (Private preview) | `CheckoutSessions` | `CheckoutSession.LineItems.Data[].price.product.identifiers.mpn` | | **Agent details** (Private preview) | `CheckoutSession.PaymentIntent` | `CheckoutSessions.PaymentIntent.agent_details` | | **OrderNo** | `CheckoutSession.PaymentIntent.LatestCharge` | `CheckoutSessions.PaymentIntent.LatestCharge.receipt_number` | #### Batch processing Instead of fulfilling each order individually, bulk fulfill orders using the [List CheckoutSessions endpoint](https://docs.stripe.com/api/checkout/sessions/list.md). ```curl # List all successful CheckoutSessions in the last hour curl https://api.stripe.com/v1/checkout/sessions?created[gt]={{TIMESTAMP}}&status=complete \ -u <>: ``` To prevent duplicate fulfillments, use the `starting_after` parameter. ```curl curl https://api.stripe.com/v1/checkout/sessions?created[gt]={{TIMESTAMP}}&status=complete&starting_after={{LAST_SESSION_ID}} \ -u <>: ``` ## Enable sales on an AI chat agent When you’re ready to sell on an AI chat agent, review the agent terms and enable the agent in the Stripe Dashboard. Stripe sends the agent an approval request that the agent must accept. To pause or stop selling on an AI chat agent, disable the agent in the Dashboard. ## Test your integration You can test your integration directly from the Dashboard in a [sandbox](https://docs.stripe.com/sandboxes.md): 1. Go to the [Agentic Commerce](https://dashboard.stripe.com/agentic-commerce) page, then click **View catalog**. 1. Hover over the product you want to test, then click **Test in Workbench**. ## Optional: Set up manual capture By default, Stripe captures payments immediately after purchase. To use manual capture, open the [Agentic Commerce Settings](https://dashboard.stripe.com/settings/agentic-commerce) page in the Dashboard and set capture mode to manual. When you enable manual capture, call the `capture` method on the `PaymentIntent`: ```curl curl -X POST https://api.stripe.com/v1/payment_intents/pi_3MrPBM2eZvKYlo2C1TEMacFD/capture \ -u ":" \ -H "Stripe-Version: 2025-09-30.preview" ``` ## Optional: Set up an order approval hook Before we confirm a payment, we check inventory based on your product catalog data and run fraud checks with [Radar](https://docs.stripe.com/radar.md) by default. To control whether we complete a purchase, configure an order approval hook. Before we complete checkout, we send an approval request to your service. You must approve or decline the request. Stripe enforces a four-second timeout for your hook. If your hook doesn’t respond within this time, Stripe declines the payment. To set up an order approval hook: 1. Specify the endpoint on the [Agentic Commerce settings](https://dashboard.stripe.com/settings/agentic-commerce) page in the Dashboard. 1. Enable **Order approvals**. 1. Implement your logic at the endpoint and use the request and response formats below. ### Request format Stripe sends the following request to your endpoint: ```typescript { type: "v1.delegated_checkout.finalize_checkout", id: string, livemode: boolean, // account ID context?: string, // request specific data data: { amount_subtotal?: number, amount_total?: number, billing_details?: { name?: string, address?: { line1?: string, line2?: string, city?: string, state?: string, postal_code?: string, country?: string } }, currency: string, email?: string, line_items_details: Array<{ id: string, unit_amount: number, quantity: number, name: string }>, payment_method_details?: { type: "card" | ..., card?: { brand: "amex" | "visa" | "master_card" | ..., country?: string, exp_month: number, exp_year: number, fingerprint?: string, funding: "credit" | "debit" | "prepaid" | "unknown", iin?: string, last4: string, wallet?: { type: "apple_pay" | "google_pay" | ... } } }, phone?: string, shipping_details?: { name?: string, address?: { line1?: string, line2?: string, city?: string, state?: string, postal_code?: string, country?: string }, }, total_details?: { amount_discount?: number, amount_shipping?: number, amount_tax?: number } } } ``` ### Response format Your endpoint must return `200` HTTP responses with the following format: ```typescript { manual_approval_details: { type: "approved" | "declined", declined?: { reason: string } }, // Connect only: set an application fee for the transaction application_fee_details?: { application_fee_amount: number, transfer_data?: { amount?: number, } } } ``` ## Optional: Set up a checkout customization hook By default, Stripe calculates taxes and shipping options for your products based on the options defined in your [product catalog](https://docs.stripe.com/agentic-commerce/product-catalog.md). To calculate taxes or shipping options and costs dynamically with your logic: 1. Specify the endpoint on the [Agentic Commerce settings](https://dashboard.stripe.com/settings/agentic-commerce) page in the Dashboard. 1. Enable **Custom tax rates** or **Custom shipping options**. 1. Implement your logic at the endpoint and use the request and response formats below. ### Request format Stripe sends the following request to your endpoint: ```typescript { type: "v1.delegated_checkout.customize_checkout", id: string, livemode: boolean, // Connected account ID context?: string, // Request specific data data: { // Used by the seller to determine whether they can set manual tax rates on line items automatic_tax: { enabled: boolean, }, currency: string, line_item_details?: Array<{ id: string, sku_id: string, unit_amount: number, amount_discount: number, amount_subtotal: number, amount_tax: number, amount_total: number, quantity: number, name: string, tax_rates: Array<{ rate: { id: string, display_name: string, percentage: number, inclusive: boolean, } // Amount of tax applied for this rate. amount: number }> }>, shipping_details?: { // Same as the shipping rate object described at https://docs.stripe.com/api/shipping_rates/object#shipping_rate_object shipping_rate?: { id: string, display_name?: string, metadata?: Map, tax_code?: string , tax_behavior: 'unspecified' | 'included' | 'excluded', fixed_amount: { amount: number, currency: 'usd' | 'cad' | etc., currency_options.: { amount: number, tax_behavior: 'unspecified' | 'included' | 'excluded', } }, delivery_estimate?: { maximum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number }, minimum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number } } }, // Same as the shipping rate object described at https://docs.stripe.com/api/shipping_rates/object#shipping_rate_object shipping_rates?: Array<{ id: string, display_name?: string, metadata?: Map, tax_code?: string, tax_behavior: 'unspecified' | 'included' | 'excluded', fixed_amount: { amount: number, currency: 'usd' | 'cad' | etc., currency_options.: { amount: number, tax_behavior: 'unspecified' | 'included' | 'excluded', } }, delivery_estimate?: { maximum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number }, minimum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number } } }, address?: { line1?: string, line2?: string, city?: string, state?: string, postal_code?: string, country?: string } }, amount_total?: number, amount_subtotal?: number, total_details?: { amount_discount?: number, amount_shipping?: number, amount_tax?: number } } } ``` ### Response format Your endpoint must return a `200` HTTP response with the following format: ```typescript { shipping_options?: Array<{ // ID of the shipping rate, or data provided to create the shipping rate. Only provide one; not both shipping_rate?: string, shipping_rate_data: { display_name?: string, fixed_amount: { amount: number, currency: 'usd' | 'cad' | etc., }, metadata?: Map, tax_code?: string , tax_behavior?: 'unspecified' | 'included' | 'excluded', // Same as the shipping rate object described at https://docs.stripe.com/api/shipping_rates/create#create_shipping_rate-delivery_estimate delivery_estimate?: { maximum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number }, minimum: { unit: 'business_day' | 'day' | 'hour' | 'month' | 'year', value: number } } }, }>, line_items?: Array<{ // Corresponding ID of the line item to update id: string, // List of tax rates to apply to this line item // Provide either `rate` or `rate_data` tax_rates: Array<{ // ID of a v1 tax rate rate?: string, // Or `rate_data`. // This will use an existing tax rate that matches the params or will create one if a matching rate does not exist rate_data?: { display_name: string, inclusive: boolean, // percentage out of 100 percentage: number, } }, }> } ``` ## Optional: Test your hooks You can test your order approval or checkout customization hook by providing a publicly accessible endpoint that can accept hook requests with a `POST` method. Set up your endpoint function so that it: 1. Handles `POST` requests with a JSON payload 1. Returns a successful status code (`200`) For local development, use a tunneling tool such as [ngrok](https://ngrok.com/) to expose your local endpoint. ### Example endpoint This code snippet is an endpoint function configured to receive `v1.delegated_checkout.finalize_checkout` requests and return a `200` responses. ```ruby require 'json' # Replace this endpoint secret with your unique endpoint secret key endpoint_secret = 'whsec_...'; # Using Sinatra post '/hooks' do payload = request.body.read # Check that you have configured webhook signing if endpoint_secret # Retrieve the event by verifying the signature using the raw body and the endpoint secret signature = request.env['HTTP_STRIPE_SIGNATURE']; begin Stripe::Webhook::Signature.verify_header( payload, signature, endpoint_secret ) rescue Stripe::SignatureVerificationError => e puts "Webhook signature verification failed. #{e.message}" status 400 end end # Handle the event payload = JSON.parse(payload) case payload["type"] when 'v1.delegated_checkout.finalize_checkout' # Check inventory and accept payment data = { manual_approval_details: { type: "approved" } } end status 200 body data.to_json end ``` ## Optional: Set up custom order URL By default, Stripe returns a [payment receipt](https://docs.stripe.com/receipts.md) to a customer after your order is confirmed. To provide your self-hosted order URL: 1. Select **Custom page** in the **Receipt type** dropdown on the [Agentic Commerce Settings](https://dashboard.stripe.com/settings/agentic-commerce) page in the Dashboard 1. Provide your order URL in the **Custom page URL** field. Your URL needs to have a `{CHECKOUT_SESSION_ID}` template variable. After your order is confirmed, Stripe automatically substitutes it with a Checkout Session ID. You can look up the Checkout Session using the ID to display the order information. ## Optional: Send incremental inventory updates In addition to uploading your product catalog, send individual product inventory updates through the Inventory Feed API. Use the same upload process as catalog uploads, but set `standard_data_format` to `inventory_feed`: ```curl # Step 1: Upload your CSV using the Files API curl https://files.stripe.com/v1/files \ -u <>: \ -F purpose=data_management_manual_upload \ -F file="@/path/to/your/file.csv;type=text/csv" # Step 2: Create an ImportSet object curl https://api.stripe.com/v1/data_management/import_sets \ -H "Stripe-Version: 2025-09-30.clover;udap_beta=v1" \ -u <>: \ -d file={{FILE_ID}} \ --data-urlencode standard_data_format="inventory_feed" ``` ## Optional: Send incremental price updates Upload your product catalog, then send individual price updates through the Price Feed API. Use the same upload process as catalog uploads and set `standard_data_format` to `price_feed`. ```curl # Step 1: Upload your CSV using the Files API curl https://files.stripe.com/v1/files \ -u <>: \ -F purpose=data_management_manual_upload \ -F file="@/path/to/your/file.csv;type=text/csv" # Step 2: Create an ImportSet object curl https://api.stripe.com/v1/data_management/import_sets \ -H "Stripe-Version: 2025-09-30.clover;udap_beta=v1" \ -u <>: \ -d file={{FILE_ID}} \ --data-urlencode standard_data_format="price_feed" ``` ## Optional: Handle refunds and disputes If a customer cancels the order on your website or through customer service after checkout succeeds, initiate a refund. If you already use the Checkout Sessions or PaymentIntents API, your existing refund flow works without changes for agentic checkouts. You can manage refunds and disputes in the Dashboard on the [Transactions page](https://dashboard.stripe.com/payments), or use the [Refunds API](https://docs.stripe.com/api/refunds.md) to handle cancellations and refunds programmatically. ## See also - [Shared Payment Tokens](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md)