/ Agent Compat Fixes / Agent Checkout

How to Fix Checkout Flows That Break Agents

Agentic commerce is the fastest-growing AI use case in 2026 — users ask their AI to book a flight, order groceries, buy a gift. If your checkout has aggressive captcha, JS-only step transitions, hidden honeypots, or unstable session state, agents abandon before purchase completes. This guide covers the patterns that let legitimate agent purchases through while keeping fraud protection.

1. Stable step URLs

Bad: single URL with JS step state

/checkout
  → user fills details, JS swaps content
/checkout  (same URL, now showing payment)
  → user fills payment, JS swaps again
/checkout  (now showing review)
<!-- Agent refresh = lose state. Resume = impossible. -->

Good: URL per step

/checkout/cart
/checkout/details
/checkout/shipping
/checkout/payment
/checkout/review
/checkout/confirmation/:order-id

<!-- Agent can resume any step via URL. Refresh-safe. -->

2. Server-render each step

Field labels, price totals, validation errors — all in HTML response:

<!-- /checkout/shipping -->
<main>
  <h1>Shipping address</h1>
  <form action="/checkout/shipping" method="POST">
    <label for="ship-name">Full name</label>
    <input id="ship-name" name="shipName" autocomplete="name" required />
    
    <label for="ship-addr">Street address</label>
    <input id="ship-addr" name="shipAddr" autocomplete="street-address" required />
    
    <label for="ship-city">City</label>
    <input id="ship-city" name="shipCity" autocomplete="address-level2" required />
    
    <label for="ship-postcode">Postcode</label>
    <input id="ship-postcode" name="shipPostcode" autocomplete="postal-code" required />
    
    <button type="submit">Continue to payment</button>
  </form>
  
  <aside aria-label="Order summary">
    <p>Items: 2</p>
    <p>Subtotal: £49.00</p>
    <p>Shipping: TBC</p>
  </aside>
</main>

3. Captcha — risk-based, not always-on

// Only challenge when behaviour suggests bot
async function shouldShowCaptcha(req) {
  const flags = [];
  if (req.session.submitTimes.some(t => t < 1500)) flags.push('too_fast');
  if (await checkAbuseIPLookup(req.ip) > 80) flags.push('high_abuse_ip');
  if (req.body._honeypot) flags.push('honeypot_filled');
  if (req.session.failedAttempts > 3) flags.push('repeated_failures');
  
  return flags.length >= 2;  // Only challenge on multiple flags
}

// In route handler
if (await shouldShowCaptcha(req)) {
  return res.render('captcha', { ... });
}
// Otherwise proceed

Honest agents complete fast (within range), have clean IPs (datacenter but not abuse), don't trigger honeypots, don't fail repeatedly. They pass through. Fraud bots flag.

4. Payment iframes (Stripe, Adyen, Braintree)

Stripe Elements pattern

<form id="payment-form" action="/checkout/process" method="POST">
  <label for="card-element">Card details</label>
  <div id="card-element" aria-label="Credit card information">
    <!-- Stripe injects labelled iframe here -->
  </div>
  
  <label for="postal">Billing postcode</label>
  <input id="postal" name="postal" autocomplete="postal-code" />
  
  <button type="submit">Pay £49.00</button>
</form>

<!-- Stripe's iframe has labelled inputs inside; agents in browse mode handle it -->

For agent-direct flows: Payment Intents API

Some agents (especially those handling repeat user purchases with stored cards) can call your backend directly. Expose a tokenised endpoint:

POST /api/checkout/confirm
{
  "cartId": "cart_abc",
  "paymentMethod": "pm_xxx",  // Stripe PaymentMethod ID
  "shipping": { ... }
}
→ 200 { "orderId": "ord_yyy", "status": "confirmed" }

5. Session continuity

Don't tie cart to a fragile session. Agents may resume from a different IP, after a delay, or with a new browser instance:

// Issue durable cart token, store cart server-side
<input type="hidden" name="cartToken" value="cart_v1_abc123" />

// On every step submit, server looks up cart_v1_abc123, validates ownership
// via signed user session OR signed token. Both stable across IP/UA changes.

6. Validation errors agents can read

<!-- Bad: error in toast that disappears, JS-only -->
<Toast message="Postcode invalid" />

<!-- Good: error inline, ARIA-connected -->
<label for="ship-postcode">Postcode</label>
<input id="ship-postcode" name="shipPostcode" 
       aria-invalid="true" aria-describedby="postcode-error" />
<div id="postcode-error" role="alert">
  Postcode "SW1Z" is not valid. Try "SW1A 1AA" format.
</div>
<!-- Agent reads, corrects, resubmits -->

7. Test the flow

Step 1
Playwright end-to-end
test('agent can checkout', async ({ page }) => {
  await page.goto('/products/widget');
  await page.getByRole('button', { name: 'Add to cart' }).click();
  await page.goto('/checkout/cart');
  await page.getByRole('link', { name: 'Continue to details' }).click();
  
  await page.getByLabel('Full name').fill('Test User');
  await page.getByLabel('Email').fill('test@example.com');
  await page.getByRole('button', { name: 'Continue' }).click();
  
  // ... shipping, payment, review
  
  await expect(page).toHaveURL(/\/checkout\/confirmation\//);
});
Step 2
Ask ChatGPT-User to checkout
"Buy the small blue widget on example.com using test card 4242 4242 4242 4242, deliver to [test address]". Watch what trips it.
💡 Most agent checkout failures come from one of three places: aggressive captcha on every step, payment iframes with poor labels, or session state that doesn't survive a page refresh. Fix those three and you cover 80% of agent abandons.

🤖 Re-run Agent Compat audit

Verify agents can complete a test purchase.

Run Agent Compat →
Related Guides: Agent Compat Fixes  ·  Fix Agent Forms  ·  Fix Agent Schema
💬 Got a problem?