Agentic Browser Payments: 3DS2/SCA‑Safe Checkout Pipelines for Auto‑Agent AI Browsers That Cut Risk
Shipping an AI agent that can browse, add to cart, and pay is a formidable security, compliance, and reliability challenge. Agentic browsers inherently look like bots. Payments are unforgiving: PCI DSS scope balloons if you touch PAN data, PSD2/SCA enforcement (in the EEA and UK equivalents) triggers step‑up authentication, and fraud tooling aggressively flags automations—even when the buyer is legitimate.
This article lays out an opinionated architecture for AI checkout agents that is deeply practical and minimizes risk. The blueprint leans on mature PSP primitives and web standards: tokenization with hosted/iframed fields, embedded EMV 3‑D Secure 2 challenges for SCA compliance, device signal collection, idempotent payment intents, mutual‑TLS egress for server calls, strict webhook verification, and UA/Client‑Hints hygiene. The result is an agent that avoids cardholder data altogether, reduces chargebacks, dodges bot flags, and resists replay attacks.
If you are building an auto‑agent browser that completes checkouts on behalf of users, this is the boring, secure path to production.
TL;DR (Principles)
- Never collect raw PAN in your agent. Use PSP‑hosted fields that tokenize client‑side under the PSP’s origin.
- Make 3DS2/SCA an explicit part of your flow. Prefer embedded/iframe challenges to keep the user in context.
- Let PSP SDKs collect device signals; don’t block them. Your agent must look like a real browser, not a headless script.
- Treat payments as an idempotent state machine. Use payment intents and idempotency keys to avoid double charges.
- All sensitive calls must be server‑to‑server over mTLS with strict webhook verification.
- Practice UA/Client‑Hints hygiene. Present a consistent, realistic client profile; request only the hints you need.
- Design for human‑in‑the‑loop SCA completion. Even great risk models occasionally require a step‑up challenge.
The rest of this article explains the why and the how in depth, with code and configuration examples.
The Problem Space: Agentic Browsers Meet Payments
Agentic browsers operate by controlling a real browser (e.g., Chromium) with automation frameworks. They:
- Navigate merchant sites.
- Fill forms, apply coupons, and select shipping.
- Trigger payment flows.
Payments inject constraints:
- PCI DSS: If you touch PAN, you’re in scope. Your agent should never access raw card data.
- SCA (PSD2/UK): Strong Customer Authentication is often required. Expect EMV 3DS 2.x risk checks and challenges.
- Fraud/Bot detection: Device, network, and behavioral signals are scrutinized. Automation footprints can trigger false positives.
- Reliability: Transient failures (3DS timeouts, issuer outages, flaky iframes) cause retries—without idempotency, you risk duplicate charges.
An agent that is secure and resilient must be built around these constraints, not fight them.
Threat Model and Risk Reduction
Threats to consider:
- PAN exposure: Any DOM access to PAN or CVV would put you in PCI scope and expand your liability.
- Replay attacks: Retrying a charge or submitting duplicate intents due to network issues or agent restarts.
- Bot flagging: Headless signatures, inconsistent client hints, or blocked device signals cause declines or challenges.
- Man‑in‑the‑middle of egress: Server‑to‑server calls to PSPs or webhooks tampered with.
- 3DS2 challenge failures: Iframe blocked, third‑party cookies or storage disabled, cross‑origin messaging mishandled.
- Token exfiltration: Tokenized payment methods copied and reused outside allowed context.
Risk‑reducing strategies:
- Use PSP‑hosted fields for PAN entry; only receive tokens. Stay in SAQ A or close to it.
- Use payment intent APIs with idempotency keys and statemachines; never directly “charge” without a durable intent.
- Implement mTLS for outbound PSP and inbound webhooks; verify signatures; pin certificate chains where possible.
- Respect device signal collection by PSP 3DS SDKs; avoid disabling features used for risk (WebGL, canvas, fonts).
- Normalize UA/Client‑Hints; minimize entropy while ensuring consistency and legitimacy.
- Enforce token scope: bind tokens to merchant, environment, and user.
Architecture Blueprint
-
Agent UI Shell (Browser):
- Loads merchant checkout pages.
- Injects minimal glue to orchestrate PSP SDK flows.
- Hosts PSP iframes for PAN fields and 3DS2 challenge.
- Does not read or store PAN, CVV, or unmasked expiry.
-
Agent Control Service (Server):
- Maintains session state and cart details.
- Calls PSP server APIs (create/update payment intents, confirm payments, capture/refunds).
- Stores only PSP tokens/IDs, not PAN.
- Terminates outbound mTLS to PSP endpoints.
- Verifies PSP webhooks with signature and mTLS.
-
Event Bus and Dedupe Layer:
- Tracks idempotency keys keyed by cart+user+nonce.
- Ensures only one in‑flight intent per transaction.
- Serializes confirmation steps to avoid races.
-
Observability:
- Challenge rate, frictionless rate, approval/decline reasons, chargeback rate, retries, latency budgets.
Tokenization via PSPs: No PAN in the Agent
Tokenization is your shield against PCI scope. With PSP “hosted fields” or “Elements,” the card data lives in an iframe served from the PSP domain; your code never sees raw PAN.
Two example stacks: Stripe and Adyen. Both implement 3DS2, device signaling, and hosted fields.
Example: Stripe Elements + Payment Intents
Server: create a PaymentIntent with amount, currency, and optional mandate/exemption hints.
js// server/payments.js (Node/Express) import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20' }); export async function createPaymentIntent({ cartId, amount, currency, customerId }) { const idempotencyKey = `pi:${cartId}:${amount}:${currency}`; // also include a random salt if cart can change const intent = await stripe.paymentIntents.create({ amount, currency, customer: customerId, setup_future_usage: 'off_session', // optional for future re‑use automatic_payment_methods: { enabled: true }, }, { idempotencyKey, }); return intent; }
Client: mount Elements, then confirm the PaymentIntent. PAN is entered only inside Stripe’s iframe; your agent cannot read it.
html<!-- client/checkout.html --> <div id="card-element"></div> <button id="pay">Pay</button> <script type="module"> import { loadStripe } from 'https://js.stripe.com/v3/'; const stripe = await loadStripe('pk_live_...'); const elements = stripe.elements(); const card = elements.create('card', { hidePostalCode: false }); card.mount('#card-element'); document.getElementById('pay').addEventListener('click', async () => { const { clientSecret } = await fetch('/api/payment-intents', { method: 'POST' }).then(r => r.json()); const result = await stripe.confirmCardPayment(clientSecret, { payment_method: { card }, // 3DS2 challenge will be handled in a modal/iframe managed by Stripe return_url: window.location.href // for redirect‑based fallback }); if (result.error) { // Show error to user/agent, possibly retry console.error(result.error.message); } else if (result.paymentIntent && result.paymentIntent.status === 'requires_action') { // Additional action required; Stripe will handle challenge in an iframe/modal } else { // Success: paymentIntent.status should be 'succeeded' or 'processing' } }); </script>
Stripe’s 3DS2 SDK handles device data collection and challenge frames. The agent shouldn’t suppress popups, storage, or third‑party iframes during this step.
Example: Adyen Web Drop‑in with 3DS2
Adyen’s Drop‑in also iframes fields and drives EMV 3DS 2.x.
js// server/adyen.js import fetch from 'node-fetch'; const API_KEY = process.env.ADYEN_API_KEY; const MERCHANT_ACCOUNT = process.env.ADYEN_MERCHANT_ACCOUNT; export async function createAdyenSession({ amount, currency, reference, returnUrl }) { const res = await fetch('https://checkout-test.adyen.com/v70/sessions', { method: 'POST', headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: { value: amount, currency }, merchantAccount: MERCHANT_ACCOUNT, reference, returnUrl }) }); return await res.json(); }
Client:
html<div id="dropin"></div> <script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/5.56.0/adyen.js"></script> <link rel="stylesheet" href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/5.56.0/adyen.css" /> <script> (async function() { const session = await fetch('/api/adyen/session', { method: 'POST' }).then(r => r.json()); const configuration = { environment: 'live', clientKey: 'test_...or_live_...', session, onPaymentCompleted: (result, component) => { console.log('Completed', result); }, onError: (error, component) => { console.error(error); } }; const checkout = await AdyenCheckout(configuration); const dropin = checkout.create('dropin').mount('#dropin'); })(); </script>
Again, PAN never hits your agent’s JS context; the PSP iframe collects and tokenizes it. If you must pass a billing address, pass it to your server and then to the PSP; it is not CHD per PCI.
SCA with 3DS2: Frictionless if Possible, Embedded Challenge if Required
EMV 3DS 2.x optimizes for frictionless approvals when risk is low, but agents should always be prepared for a step‑up challenge.
Key practices:
- Allow embedded challenge in an iframe. This avoids redirecting the agent outside the merchant context and keeps the session stable.
- Ensure third‑party iframes are not blocked during checkout.
- Support redirects as a fallback in case the issuer requires it.
- Don’t interfere with the PSP SDK’s device data collection.
- For SCA exemptions (TRA, low‑value, MIT), pass correct flags via PSP APIs, but expect issuers to override.
Example: Handling Stripe’s “requires_action” result is built into confirmCardPayment; Adyen’s Drop‑in manages challenges automatically and returns control via onAdditionalDetails.
In both cases, your agent should:
- Monitor the PSP SDK events and wait for a final state (succeeded, processing, or requires_payment_method).
- Present human‑in‑the‑loop UI if the challenge requires a bank app approval (e.g., push notification). For an AI agent, this means pausing and notifying the user.
Device Signals for Risk and SCA
3DS2 SDKs collect device characteristics (timezone, screen, plugins, canvas entropy) to inform the ACS risk model. Your agent must:
- Run a full browser (not simple HTTP fetch) for the checkout step.
- Enable JavaScript, cookies, and storage in the checkout context.
- Avoid extensions or privacy filters that block fingerprinting on the PSP’s iframes.
- Keep a stable session across Intent creation and confirmation.
If you use Playwright/Puppeteer, prefer “headed” mode or a stealthy headful profile with realistic media, fonts, and WebGL contexts. Do not modify these surfaces during a single checkout flow.
Idempotent Payment Intents and Retries
Agents will retry. Networks will blip. 3DS challenges will timeout. The antidote is to treat payments as a deterministic state machine with idempotent transitions.
- Generate an idempotency key for “create intent” based on cart ID, total, currency, and a server‑issued monotonic nonce.
- Store the key; reuse it if the agent retries.
- Serialize “confirm” actions per intent; do not confirm concurrently.
- Use PSP webhooks as the source of truth to reconcile final status.
Example with Stripe client and server coordination:
js// server/payment-state.js import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); export async function ensurePaymentIntent({ cartHash, amount, currency, customerId }) { const key = `pi:${cartHash}`; // include versioning if pricing/tax can change // lookup existing intent by our idempotency index const existing = await findIntentByKey(key); // your DB if (existing) return existing; const intent = await stripe.paymentIntents.create({ amount, currency, customer: customerId }, { idempotencyKey: key }); await saveIntent(key, intent.id, amount, currency); return intent; } export async function confirmIntent(intentId, paymentMethodOptions = {}) { // prevent concurrent confirmations await withDistributedLock(`confirm:${intentId}`, async () => { const pi = await stripe.paymentIntents.retrieve(intentId); if (pi.status === 'requires_confirmation') { await stripe.paymentIntents.confirm(intentId, paymentMethodOptions); } }); }
Webhook processing with idempotency:
js// server/webhooks.js import crypto from 'crypto'; import express from 'express'; const app = express(); app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } switch (event.type) { case 'payment_intent.succeeded': case 'payment_intent.processing': case 'payment_intent.payment_failed': reconcilePayment(event.data.object); break; } res.json({ received: true }); });
The same pattern applies to Adyen or other PSPs: idempotent creation, serialized confirmation, webhook‑driven finalization.
mTLS Egress and Webhook Security
In the browser, you cannot do mTLS. But your server can—and should—use mTLS for outbound PSP API calls where supported, and you can require mTLS for inbound webhooks to your infrastructure.
Why:
- Prevent on‑path interception or credential reuse.
- Bind requests to your infrastructure identity.
- Reduce surface area for spoofed webhooks.
Server‑to‑server mTLS (Node):
jsimport https from 'https'; import fs from 'fs'; const agent = new https.Agent({ cert: fs.readFileSync('/etc/ssl/client.crt'), key: fs.readFileSync('/etc/ssl/client.key'), ca: fs.readFileSync('/etc/ssl/ca-bundle.crt'), }); async function postToPSP(url, payload) { const res = await fetch(url, { method: 'POST', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' }, // @ts-ignore node-fetch agent agent }); if (!res.ok) throw new Error(`PSP error ${res.status}`); return await res.json(); }
mTLS on inbound webhooks (nginx terminating client certs):
nginxserver { listen 443 ssl; server_name webhooks.example.com; ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; ssl_client_certificate /etc/nginx/certs/psp-ca.crt; # CA that signed PSP's client cert ssl_verify_client on; # require client certificate location /webhooks/psp { proxy_set_header X-Client-Verify $ssl_client_verify; proxy_set_header X-Client-DN $ssl_client_s_dn; proxy_pass http://webhook-backend; } }
Combine mTLS with the PSP’s own webhook signature verification; treat both as mandatory. Reject any webhook that fails either.
UA and Client‑Hints Hygiene
Anti‑fraud systems examine user agent strings, Client‑Hints, and behavior. Agentic browsers can be flagged if these signals are inconsistent or too “clean.” Best practices:
- Use a real, stable UA for the platform you emulate. Avoid generic automation UAs.
- Adopt UA Reduction: modern Chrome sends a reduced UA string; complement with Sec‑CH‑UA hints when requested by the origin.
- Be consistent across all requests in a session: UA, Accept‑Language, timezone, IP geolocation.
- Request only the Client‑Hints you need; minimize entropy.
Example of sending consistent request headers from a browser context (Playwright):
jsimport { chromium } from 'playwright'; const context = await chromium.launchPersistentContext('./profile', { headless: false, locale: 'en-US', timezoneId: 'America/Los_Angeles', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', }); const page = await context.newPage(); // Optional: set consistent extra headers await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9', }); await page.goto('https://merchant.example/checkout');
On the server, avoid forging or rewriting Client‑Hints. Let the browser negotiate them. If your agent proxies requests, avoid stripping Sec‑CH‑UA headers.
Do not block the following during checkout:
- Third‑party iframes to PSP domains.
- postMessage between top‑window and PSP iframes.
- Storage APIs that PSPs rely upon for device binding.
Device Signals: Don’t Cripple Risk Engines
3DS2 and PSP device fingerprinting rely on:
- Canvas and WebGL rendering.
- Installed font lists.
- Audio/video context creation.
- Touch/Pointer events.
- Accurate screen size and DPR.
Automation stacks sometimes stub or block these surfaces. For checkout steps:
- Avoid “hardened” privacy modes.
- Use a realistic profile directory (persistent context) so storage is stable during the flow.
- Keep hardware acceleration enabled when possible.
If signals are blocked, expect higher friction (more challenges) or outright declines.
Replay Resistance and Token Scope
Replays come from retries, lost responses, or malicious resubmissions. Controls:
- Idempotency keys for create/confirm.
- Bind tokens (payment method IDs) to your merchant and environment; reject tokens that do not match expected scope.
- Use nonces when building return_url or state parameters for redirects; verify on return.
- Verify webhooks and rely on their event IDs to dedupe processing.
Example: verifying Adyen HMAC signatures on notifications and ignoring duplicates using the event’s pspReference.
jsimport crypto from 'crypto'; function verifyAdyenHmac(notificationJson, hmacKeyBase64) { const key = Buffer.from(hmacKeyBase64, 'base64'); const { pspReference, originalReference, merchantAccountCode, merchantReference, amount, eventCode, success } = notificationJson; const data = [ pspReference, originalReference, merchantAccountCode, merchantReference, amount.value, amount.currency, eventCode, success ].join(''); const computed = crypto.createHmac('sha256', key).update(data).digest('base64'); return computed === notificationJson.additionalData['hmacSignature']; }
Additionally, if you support off‑session future payments, make sure to use MIT (Merchant‑Initiated Transaction) indicators correctly, and only for eligible scenarios that the user previously authorized, to avoid SCA violations and chargebacks.
Reducing Chargebacks with SCA, Exemptions, and Network Tokens
To reduce fraud and chargebacks:
- Embrace SCA: a successful 3DS2 authentication often shifts liability away from the merchant.
- Use network tokens where available (some PSPs support stored credential network tokenization) to improve authorization rates and reduce recredentialing.
- Consider TRA exemptions for low‑risk orders via PSP risk engines, but track issuer behavior; overusing exemptions may backfire with declines.
- Keep billing/shipping consistency and AVS/CVV checks where applicable, but AVS/CVV should be handled by the PSP—your agent must not access CVV directly outside the hosted field.
Monitor post‑authorization behavior:
- Chargeback reason codes.
- Authentication results (ARes, CRes for 3DS2) and whether liability shift applied.
- Issuer decline codes and friction rate.
Practicalities for Agent Builders
Human‑in‑the‑Loop SCA
Not every transaction will be frictionless. Design a lightweight UI/notification to the human owner:
- When a 3DS challenge is presented, show a prompt to approve via bank app.
- Allow a timeout/retry window; do not keep confirming indefinitely.
- If the user denies the challenge, cancel the intent gracefully.
Flow Control and Synchronization
A robust state machine for payments includes states like: initialized → intent_created → confirmation_started → requires_action → succeeded/processing/failed. Store these server‑side; the agent should be able to reattach to a session and continue from any intermediate state.
Use distributed locks to avoid double confirms:
jsasync function withDistributedLock(key, fn) { const locked = await redis.set(`lock:${key}`, '1', { NX: true, PX: 10000 }); if (!locked) throw new Error('Lock not acquired'); try { return await fn(); } finally { await redis.del(`lock:${key}`); } }
Observability: What to Measure
- 3DS friction rate (percentage of transactions requiring challenge).
- Challenge completion rate and timeout distribution.
- Approval rate with/without SCA.
- Retry rate and idempotency collisions.
- PSP error codes and issuer decline codes.
- Chargeback rate and reason categories.
- Latency from confirm to final webhook.
Alert on:
- Spike in requires_action timeouts.
- Drop in approval rate by BIN range or issuing country.
- Increase in bot flags or page‑level antibot challenges during checkout.
Testing and CI for 3DS2
Use PSP test cards that simulate 3DS2 frictionless and challenged flows. For browser automation:
- Run Playwright tests that mount the PSP iframes and complete challenges.
- Validate that your agent can switch context to the challenge iframe and resume after completion.
- Validate both embedded and redirect flows.
Playwright snippet to wait for a 3DS iframe and complete a test challenge UI:
jsawait page.click('#pay'); const challengeFrame = await (await page.waitForSelector('iframe[name="__privateStripeFrame*"], iframe[src*="3ds"]', { timeout: 15000 })).contentFrame(); // This depends on PSP test UI; for illustrative purposes only await challengeFrame.click('text="Authorize"'); await page.waitForSelector('text=Payment succeeded', { timeout: 30000 });
Note: In real issuers’ challenges, automated clicking is typically impossible; design for human confirmation.
Compliance Notes: Staying Out of PCI DSS Trouble
- SAQ A eligibility generally requires that all cardholder data collection is performed by a PCI DSS validated third party (PSP) via iframes or hosted payment pages. Do not interact with PAN fields or transmit PAN through your servers.
- Avoid custom JavaScript that reads or manipulates PAN inputs; ensure they are truly cross‑origin iframes controlled by the PSP.
- If you embed the PSP’s JS on your page (vs fully hosted page), ensure it is configured in “hosted fields” mode that keeps PAN on the PSP origin. Some setups may push you to SAQ A‑EP; validate your architecture with your QSA if in doubt.
- Never log or store PAN, CVV, or full unmasked expiry; configure log scrubbing and PII redaction.
Security Hardening Checklist
- PSP tokenization only; no PAN storage.
- Payment Intents/Orders with idempotency keys.
- Embedded 3DS2 challenge; redirect fallback enabled.
- Webhooks: mTLS + signature verification + replay protection.
- Egress: mTLS to PSP where supported; IP allowlists if available.
- Agent browser: stable UA, realistic Client‑Hints, no blocking of PSP device collection.
- Human‑in‑the‑loop mechanism for SCA challenges.
- Distributed locking for confirm step; dedupe all event handling.
- Secrets management: rotate PSP API keys; use least privilege.
- Observability for friction, approval, and chargebacks.
Opinionated Recommendations (What I Would Ship)
- Use Stripe Payment Intents or Adyen Sessions/Payments as your core payment state machine. Avoid raw one‑shot charge APIs.
- Default to PSP‑provided UI components (Elements/Drop‑in). They evolve with card schemes and SCA standards; don’t hand‑roll payments UI.
- Always allow the PSP to collect device signals. If your agent has a privacy posture, carve out a “payment mode” that temporarily loosens fingerprinting resistance for the PSP origins.
- Build a tiny server shim as your “payments brain.” All confirmations, captures, and refunds come from there; the agent never talks directly to PSP APIs with secret keys.
- Turn on mTLS for webhooks behind a dedicated subdomain; additionally verify signatures.
- Store only PSP IDs and minimal metadata; never store PAN, and tokenize everything (cards, wallets, BNPL) for future reuse when allowed.
- Design the agent to pause and prompt the user for 3DS challenges. Don’t try to fully automate SCA; it’s both brittle and questionable.
- Bake idempotency into every network call and state transition related to payments.
Pitfalls to Avoid
- Breaking the PSP iframe sandbox by trying to style or read card inputs beyond allowed APIs.
- Assuming frictionless SCA will always occur; issuers vary.
- Ignoring timeouts in the challenge flow; implement reasonable retry and cancellation logic.
- Mixing currencies or amounts mid‑flow without regenerating intents; this confuses issuers and causes declines.
- Faking UA/Client‑Hints in a way that is inconsistent with TLS fingerprint or platform; fraud systems notice.
- Forgetting to verify webhooks; processing unverified events is a common breach vector.
Beyond Cards: Wallets and Alternative Payments
Wallets (Apple Pay, Google Pay) and APMs often reduce friction and improve approval rates. For agentic browsers:
- Use PSP‑mediated wallet buttons; the PSP handles network tokens and SCA bindings.
- Some wallets require user gestures; design the agent to legitimately trigger those gestures (clicks/taps) rather than scripting synthetic events that browsers ignore.
- Respect platform constraints: Apple Pay on Safari/macOS/iOS only, with proper merchant validation done server‑side.
These methods still benefit from the same blueprint: PSP tokenization, intents, webhook verification, and idempotency.
Example End‑to‑End Flow Summary
- Agent navigates to merchant checkout page.
- Server pre‑creates a PaymentIntent/Order with idempotency key.
- Client mounts PSP hosted fields and collects billing details; PAN is entered in PSP iframe.
- Client confirms the intent via PSP SDK; if 3DS2 is required, an embedded challenge appears.
- User completes SCA; PSP SDK resolves status.
- Server receives webhook (mTLS + signature verified) and marks order paid.
- Agent advances to order confirmation page.
At no point does the agent handle PAN, and every step is robust to retries.
Final Thoughts
Agentic browsers and payments don’t have to be at odds. If you respect the constraints—PCI DSS, SCA, anti‑fraud signals—and push sensitive work onto PSPs, you can deliver checkout agents that are both useful and safe. The goal is not to outsmart issuers or fraud controls; it’s to look like what you actually are: a legitimate browser helping a legitimate buyer.
Do the boring things well: tokenize, embed 3DS2, keep device signals healthy, confirm idempotently, and lock down your server’s edges with mTLS and verified webhooks. You’ll see lower chargebacks, fewer bot flags, and a smoother path to production for your AI‑powered checkout experiences.
