Learn

How to Implement ACP: A Merchant Integration Walkthrough

Andrew McPherson

Depth · Advanced

Good for: Builders

Implementing the Agentic Commerce Protocol (ACP) as a merchant comes down to three responsibilities: publish a product feed so agents can find what you sell, host an Agentic Checkout API so an agent can build and complete a cart with you, and accept a Shared Payment Token so you get paid without your store ever touching a card. This guide walks through each, with the real endpoints and payloads from the spec. If you are already on Stripe, which co-authored ACP, most of this is provided and the lift is small; if you are on another processor, you implement the same open spec directly.

Throughout, remember the core principle: you stay the merchant of record. ACP standardizes the checkout handshake; it does not take over your store, your fulfillment, your tax, or your customer relationship.

How the pieces fit

The agent platform (for example ChatGPT) is the client. You, the merchant, are the server. The agent calls endpoints you host to create a checkout session, update it as the buyer chooses options, and complete it. Payment is tokenized by a payment provider into a Shared Payment Token that is scoped to you and to one exact cart total, and that token is handed to you on the final call. You charge it through your processor and return an order.

So an ACP integration is a small REST API you implement plus a payment step, not a connection you make to someone else’s system. That is what lets one integration work across every agent surface that speaks ACP.

Step 1: Publish a product feed

Before an agent can buy from you, it has to know what you sell. ACP merchants provide a structured product feed: each item with the fields an agent needs to choose and transact, including title, description, price, availability, images, and identifiers. The single most important property of the feed is that it matches your live store exactly. Agents transact against the data you publish, so a price or stock figure that has drifted from reality is the most common cause of a failed or wrong agent checkout. Keep the feed complete, structured, and current; on Stripe and similar platforms, feed syndication is largely handled for you. For the protocol-agnostic detail on getting this right, see how to prepare your product catalog for agentic commerce.

Step 2: Implement the Agentic Checkout API

This is the heart of the integration. You host five endpoints under a /checkout_sessions base path. Every request carries a few headers: Authorization (a bearer API key the agent platform presents, which you verify), Content-Type: application/json, and API-Version (a date such as 2026-01-30) are required; Idempotency-Key, Signature, Timestamp, and Request-Id are optional but worth honoring.

POST /checkout_sessions                          create a session       -> 201 CheckoutSession
POST /checkout_sessions/{checkout_session_id}     update a session       -> 200 CheckoutSession
GET  /checkout_sessions/{checkout_session_id}     retrieve a session     -> 200 CheckoutSession
POST /checkout_sessions/{checkout_session_id}/complete  complete         -> 200 CheckoutSessionWithOrder
POST /checkout_sessions/{checkout_session_id}/cancel    cancel           -> 200 CheckoutSession

Note that update is a POST to the session path (there is no PUT or PATCH), and create returns 201 while the rest return 200.

Create a session

The agent sends the line items, the settlement currency, and the capabilities it supports. Your job is to price the cart and return a full session.

POST /checkout_sessions
{
  "line_items": [
    { "item": { "id": "sku_running_shoe_42" }, "quantity": 1 }
  ],
  "currency": "usd",
  "buyer": { "email": "buyer@example.com" },
  "fulfillment_details": {
    "address": {
      "name": "Jane Doe", "line_one": "185 Berry Street", "city": "San Francisco",
      "state": "CA", "country": "US", "postal_code": "94107"
    }
  }
}

You respond with a CheckoutSession: an id, a status, the priced line_items, the totals, the fulfillment_options you can offer, any messages, your policy links, and your capabilities. Every monetary amount is an integer in minor units, so 4500 means 45.00 dollars.

{
  "id": "cs_01HV3P3ABC123",
  "status": "ready_for_payment",
  "currency": "usd",
  "line_items": [
    { "id": "li_001", "item": { "id": "sku_running_shoe_42" }, "quantity": 1,
      "totals": [{ "type": "subtotal", "display_text": "Subtotal", "amount": 12000 }] }
  ],
  "fulfillment_options": [
    { "type": "shipping", "id": "ship_standard", "title": "Standard (3-5 days)",
      "totals": [{ "type": "fulfillment", "display_text": "Shipping", "amount": 0 }] }
  ],
  "totals": [
    { "type": "subtotal", "display_text": "Subtotal", "amount": 12000 },
    { "type": "tax", "display_text": "Tax", "amount": 1080 },
    { "type": "total", "display_text": "Total", "amount": 13080 }
  ],
  "messages": [],
  "links": [{ "type": "return_policy", "url": "https://store.example.com/returns" }],
  "capabilities": {}
}

The session lifecycle

The status field drives the whole flow, and it is worth implementing as a state machine. The spec defines these values: incomplete, not_ready_for_payment, requires_escalation, authentication_required, ready_for_payment, pending_approval, complete_in_progress, completed, canceled, in_progress, and expired. The agent will not attempt payment until you report ready_for_payment, so your pricing, fulfillment, and validation logic should resolve the session to that status (or surface a clear message explaining what is missing).

Update a session

As the buyer chooses a shipping option or signs in, the agent posts a partial update. For example, selecting a fulfillment option:

POST /checkout_sessions/cs_01HV3P3ABC123
{
  "selected_fulfillment_options": [
    { "type": "shipping", "option_id": "ship_standard", "item_ids": ["li_001"] }
  ]
}

You recompute and return the updated session, including refreshed totals and a new status.

Step 3: Complete the checkout with a Shared Payment Token

This is the step people most often get wrong, so it is worth being precise. When the buyer pays, the payment provider issues a Shared Payment Token: a single-use credential scoped to your merchant id and the exact cart total, which the agent then hands to you. The token is delivered inside payment_data on the complete call, and the schema-correct shape is nested:

POST /checkout_sessions/cs_01HV3P3ABC123/complete
{
  "buyer": { "first_name": "Jane", "last_name": "Doe", "email": "buyer@example.com" },
  "payment_data": {
    "handler_id": "handler_stripe_card",
    "instrument": {
      "type": "card",
      "credential": { "type": "spt", "token": "spt_123" }
    }
  }
}

The credential.type of spt marks it as a Shared Payment Token, and handler_id names one of the payment handlers you advertised in your session capabilities. Do not send a flat { "token": "spt_123", "provider": "stripe" }; that form appears in some older examples but is not valid against the current schema, which rejects unknown fields. Charge the token through your processor, and on success return a CheckoutSessionWithOrder, the session plus an order:

{
  "id": "cs_01HV3P3ABC123",
  "status": "completed",
  "order": {
    "id": "ord_88212",
    "checkout_session_id": "cs_01HV3P3ABC123",
    "permalink_url": "https://store.example.com/orders/88212",
    "status": "confirmed"
  }
}

The order.status progresses through confirmed, processing, shipped, and delivered as you fulfill.

Because the token is scoped to one merchant and one total and is single-use, the agent never holds reusable payment power and your store never sees the raw card. Merchants on Stripe can accept the token with minimal code. Merchants on other processors tokenize and charge through the protocol’s separate Delegate Payment API (POST /agentic_commerce/delegate_payment), which vaults a card under an allowance that fixes the max_amount, currency, merchant_id, checkout_session_id, and an expires_at, the same scoping by another route. For the concepts behind all this, see how AI agents pay.

Security essentials

Three things keep the integration safe. Verify the bearer token on every request, so only authorized agent platforms can drive a checkout. Honor the optional Signature and Timestamp headers when present, by validating the detached signature over the request body within a short time window, to prevent tampering and replay. And treat the Idempotency-Key seriously: store it and return the original result on a retry, so a network retry never creates a duplicate order or charge. The Delegate Payment API uses the same idempotency and signing conventions.

Errors versus messages

ACP separates two kinds of problem, and your implementation should too. A protocol-level failure (malformed request, bad auth, rate limit) returns an Error object with a type, code, and message and an appropriate 4xx or 5xx status. A business-level condition that is part of a valid checkout (an item went out of stock, a coupon is invalid, a sign-in or 3-D Secure step is required) is not an HTTP error; it is a message inside an otherwise valid 200 session, with a code such as out_of_stock or requires_3ds and a matching status. Returning the right one is what lets an agent recover gracefully rather than giving up.

Test, then go live

Build against the spec’s examples and your processor’s sandbox first. Walk the full lifecycle: create a session, update it to select fulfillment, complete it with a test Shared Payment Token, and confirm you return an order. Check the unhappy paths too: an out-of-stock item, an expired session, an idempotent retry. When it behaves, going live is a matter of your processor enabling agentic payments and your feed being present and accurate. Then the same integration works across every ACP agent surface, which is the entire point of adopting a standard rather than building one deal at a time.

Where to go next

The conceptual companion to this page is ACP explained, and the authoritative detail lives in the ACP spec on GitHub, linked below. If your next step is exposing your own tools or data to agents rather than selling through them, see how to build a remote MCP server. And for where ACP sits among the other standards, see the protocol stack.

FAQ

What does a merchant actually have to build? A product feed, an Agentic Checkout API (create, update, get, complete, cancel a session), and payment handling that accepts a Shared Payment Token on completion. On Stripe much of this is provided.

How is the payment passed without my store seeing the card? On complete, the agent sends payment_data whose instrument.credential.token is a Shared Payment Token (credential.type of spt), scoped to your merchant id and the exact total. You charge that token; the raw card never reaches you.

Do I have to be on Stripe? No. Stripe provides a reference implementation, but ACP is an open standard. Other processors implement the same spec and use the Delegate Payment API or their own equivalent.

What is the most common mistake? Sending a flat { token, provider } payment shape (not schema-valid; use the nested instrument.credential.token), and letting feed prices or stock drift from your live store, since amounts are integers in minor units and mismatches break checkout.

Primary sources

  1. Agentic Commerce Protocol specification (GitHub) · GitHub
  2. ACP Agentic Checkout API (OpenAPI, 2026-01-30) · GitHub, 2026-01-30
  3. ACP Delegate Payment API (OpenAPI, 2026-01-30) · GitHub, 2026-01-30
  4. Stripe Agentic Commerce documentation · Stripe