# Manage your agentic commerce integration

Configure checkout controls, keep product data accurate, offer promotions, and manage payments after checkout.

After you set up your agentic commerce integration, configure how Stripe handles checkout decisions, product availability, promotions, payment capture, and post-purchase operations. Use hooks and current catalog data to keep checkout details accurate, add promotions to increase conversion, choose when to capture payments, and manage refunds and disputes with your existing Stripe workflows.

## Prevent out-of-stock and price failure

Keep the rate of checkout attempts that fail because an item is unavailable below 5%. The right approach depends on how quickly your inventory changes, and you can combine approaches.

**More frequent feed refreshes**: For most sellers with relatively stable inventory, keeping your feed current on the [recommended cadence](https://docs.stripe.com/agentic-commerce/for-sellers.md#upload-feed) is enough to stay within the threshold.

**Real-time availability check**: Set up the [product price and availability hook](https://docs.stripe.com/agentic-commerce/for-sellers/manage.md#product-price-availability). Stripe calls this endpoint immediately before checkout completes to return the current price and confirm that the item is still in stock. It operates independently of your feed refresh frequency and is particularly valuable if:

- Your inventory changes quickly, such as during flash sales or limited drops where items sell out in minutes.
- You sell on multiple channels and a single item can be purchased elsewhere between feed refreshes.

When you enable the hook, Stripe:

- Calls your endpoint on behalf of agents to query product availability.
- Manages request rates so your systems aren’t overwhelmed.
- Proactively calls the hook for items with low inventory so availability data stays current between feed refreshes.

> Your product price and availability hook must respond within 4 seconds. If the hook times out, Stripe falls back to your feed data and continues checkout. This differs from the approval hook behavior, where a timeout causes Stripe to decline the checkout.

## Offer promotion

AI agents actively offer promotions to shoppers. Sellers with active promotions consistently see higher conversion rates than equivalent products without them.

Use the promotion feed to upload discount codes, percentage-off deals, fixed-amount discounts, and free shipping offers. You can target promotions to specific products or your entire catalog, set automatic application at checkout, or require a redemption code. The promotion feed references products by `id` or `item_group_id` from your catalog. Stripe ignores promotions that don’t match active product IDs.

To get started, see the [promotion feed specification](https://docs.stripe.com/agentic-commerce/product-feed.md#promotion-feed-spec).

## Set up manual capture

By default, Stripe captures payments immediately after purchase. To capture agentic payments later, 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/{{PAYMENTINTENT_ID}}/capture \
  -u "<<YOUR_SECRET_KEY>>:"
```

## 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 API or PaymentIntents API, your existing refund flow works without changes for agentic checkouts. Manage refunds and disputes on the [Transactions page](https://dashboard.stripe.com/payments) in the Dashboard, or use the [Refunds API](https://docs.stripe.com/api/refunds.md) to handle cancellations and refunds programmatically.

## Optional: Set up an order approval hook

Before we confirm a payment, we check inventory based on your product catalog data and might run fraud checks depending on your [Radar](https://docs.stripe.com/radar.md) setup. To control whether we complete a purchase, configure an order approval hook. Before we complete the checkout flow, 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.
2. Enable **Order approvals**.
3. Implement your logic at the endpoint and use the request and response formats below.

> Stripe returns a `424` status code to agents if your endpoint returns a non-`2xx` status code. Some agents might retry these API requests, so you must make sure that your endpoint is idempotent.

### 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_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
      }>
    }>,
    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
    },
    metadata?: Map<string,string>
  }
}
```

### 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,
    }
  },
  // will be propagated to Checkout Session metadata
  metadata?: Map<string,string>
}
```

## 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-feed.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.
2. Enable **Custom tax rates** or **Custom shipping options**.
3. Implement your logic at the endpoint and use the request and response formats below.

> Stripe returns a `424` status code to agents if your endpoint returns a non-`2xx` status code. Some agents might retry these API requests, so you must make sure that your endpoint is idempotent.

### 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<string,string>,
        tax_code?: string ,
        tax_behavior: 'unspecified' | 'inclusive' | 'exclusive',
        fixed_amount: {
          amount: number,
          currency: 'usd' | 'cad' | etc.,
          currency_options: {
            <currency>: {
              amount: number,
              tax_behavior: 'unspecified' | 'inclusive' | 'exclusive',
            }
          }
        },
        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<string,string>,
        tax_code?: string,
        tax_behavior: 'unspecified' | 'inclusive' | 'exclusive',
        fixed_amount: {
          amount: number,
          currency: 'usd' | 'cad' | etc.,
          currency_options: {
            <currency>: {
              amount: number,
              tax_behavior: 'unspecified' | 'inclusive' | 'exclusive',
            }
          }
        },
        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
    },
    metadata?: Map<string,string>
  }
}
```

### 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<string,string>,
      tax_code?: string ,
      tax_behavior?: 'unspecified' | 'inclusive' | 'exclusive',
      // 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,
      }
    },
  }>,
  // will be propagated to Checkout Session metadata
  metadata?: Map<string,string>
}
```

## Optional: Set up a product price and availability hook

Implement this hook to provide agents with real-time pricing and inventory data. Without it, agents rely on your uploaded product catalog, which can result in out-of-stock errors or price mismatches at checkout.

Agents can call this endpoint when inventory is low or before showing a product to a buyer. Stripe can also call the endpoint to keep catalog data current.

To set up a product price and availability hook:

1. Specify the endpoint on the [Agentic Commerce settings](https://dashboard.stripe.com/settings/agentic-commerce) page in the Dashboard.
2. Enable **Product price and availability**.
3. 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: "delegated_commerce.product_price_availability",
  id: string,
  livemode: boolean,
  // The ID of the merchant whose data is requested
  context?: string,
  // request specific data
  data: {
    sku_id: string,
  }
}
```

### Response format 

Your endpoint must return a `200` HTTP response with the following format:

```typescript
{
  sku_id: string,
  merchant_id: string,  // must match the context field from the request
  deleted?: boolean,    // when true, the product has been deleted; omit availability and price
  availability?: {      // required unless deleted is true
    status: "in_stock" | "out_of_stock" | "preorder" | "backorder",
    quantity?: number,          // current inventory count
    availability_date?: string  // ISO 8601 date when a preorder item becomes available
  },
  price?: {             // required unless availability.status is "out_of_stock" or deleted is true
    unit_amount: number,        // current selling price in smallest currency unit
    currency: string            // ISO 4217 currency code
  },
  sale_price?: {
    unit_amount: number,        // sale price in smallest currency unit
    currency: string,           // ISO 4217 currency code
    start_date: string,         // ISO 8601 sale start date
    end_date: string            // ISO 8601 sale end date
  },
  as_of?: number                // Unix timestamp of when this data was last accurate
}
```

## 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
2. 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 shows an endpoint function that receives `v1.delegated_checkout.finalize_checkout` requests and returns a `200` response. You can find the signing secret under the **Agentic Commerce Extension** event destination in your webhook settings in the [Developer Dashboard](https://docs.stripe.com/development/dashboard.md).

#### Ruby

```ruby
require 'json'
require 'sinatra'
require 'stripe'

set :port, 4242

# Replace with your endpoint's secret from your webhook settings
endpoint_secret = 'whsec_...'

# Using Sinatra
post '/hooks' do
  payload = request.body.read
  sig_header = request.env['HTTP_STRIPE_SIGNATURE']

  # Verify the webhook signature
  begin
    Stripe::Webhook::Signature.verify_header(
      payload, sig_header, endpoint_secret
    )
  rescue Stripe::SignatureVerificationError => e
    status 400
    return
  end

  # Handle the payload
  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
```
