Payment Intents and Hosted Actions: The Checkout Flow Between Intent and Settlement
A SaaS developer adds checkout to their product. A customer clicks "buy." The payment clears. The developer has never written a line of crypto code, and the customer never connected a wallet. This is the design target for the @cai.com checkout flow. The API behind it is a three-layer system: a payment intent that tracks state, a hosted action that replaces wallet signatures with a browser click, and a polling loop that turns that click into settlement.
The Intent
Every payment starts with an intent. The GET /payment-intent-status endpoint is the read side of a state machine. You pass an intent id, and the response tells you where the payment is in its lifecycle. The intent model is partial-live in the current API, the system honors gap_id fields in responses and evolves as checkout scenarios expand, but the state transitions are already stable enough to build production flows around.
GET /payment-intent-status?id=int_abc123
Returns one of: pending, awaiting_user_confirmation, confirmed, completed, or failed. The transition from pending to awaiting_user_confirmation happens when the system receives a valid payment creation that needs the user to authorize it. The transition from confirmed to completed happens after the on-chain settlement reaches the required confirmation depth.
This state machine is what separates a checkout flow from a raw transfer. A raw transfer (POST /wallet-custodial-transfer) is fire-and-forget. You either get a tx hash or an error. An intent allows the system to hold state at each step, pause for user action, and resume automatically.
The Hosted Action
The hosted action link is the mechanism that bridges the gap between an API-driven agent and a human user who needs to confirm a payment. The endpoint is POST /create-hosted-action with a structured JSON body. The critical field is action_type. For payments, you pass deposit or a specific payment-related action type.
POST /create-hosted-action
Content-Type: application/json
{
"action_type": "deposit",
"amount_usd": 50,
"chain": "ETH",
"token": "usdc",
"local_part": "alice"
}
The response returns a URL:
{
"ok": true,
"url": "https://cai.com/act/a1b2c3d4e5f6",
"expires_at": "2026-08-14T07:00:00Z"
}
This URL serves a single-tap confirmation page. The user opens it, sees the amount, the destination, and a confirm button. No wallet extension. No gas fee in their browser. No seed phrase. They tap confirm, and the system moves the intent from awaiting_user_confirmation to confirmed.
The hosted action is the product insight that makes agent-initiated payments work in practice. An agent can create an intent, push a confirmation URL to the user's dashboard or email, and wait. The agent never handles the private key. The user never sees an opaque transaction request. Both sides of the trade get the abstraction they need.
The Polling Loop
Once the intent reaches confirmed, the agent polls for settlement. The polling endpoint is the same GET /payment-intent-status, now returning completed with a tx_hash.
GET /payment-intent-status?id=int_abc123
{
"ok": true,
"id": "int_abc123",
"status": "completed",
"tx_hash": "0x7a8b9c0d1e2f...",
"chain": "ETH",
"confirmations": 12,
"settled_at": "2026-08-14T06:32:00Z"
}
The polling cadence depends on the chain. EVM chains settle in seconds to a couple of minutes. BSC and Polygon are faster. Tron is comparable to EVM. The intent response includes confirmations and a minConfirmations threshold so the agent knows when settlement is considered final.
For agents that need asynchronous notification instead of polling, the POST /transfer-notify-register endpoint accepts an optional webhook_url parameter. When the transfer settles, the system calls the webhook with the tx hash and confirmation count. This is the right pattern for high-volume checkout flows where polling every 5 seconds on 10,000 active intents would be wasteful.
The Full Flow
Putting the three layers together, an end-to-end checkout looks like this:
The merchant's backend calls
POST /wallet-custodial-transfer(or creates amarketplace_order) to initiate payment. The response includes an intent id.The backend creates a hosted action link via
POST /create-hosted-actionand returns the URL to the merchant's frontend.The merchant redirects the customer to the hosted action page. The customer sees the amount, the merchant name, and a single confirm button.
The customer confirms. The backend receives a callback (or the frontend polls the intent status) showing
confirmed.The backend starts polling
GET /payment-intent-statusfor the intent id, or waits for the webhook viaPOST /transfer-notify-register.The intent transitions to
completed. The backend records the tx hash and updates the order status.
The remarkable property of this flow is that step 3 never shows the customer a blockchain address, a gas fee estimate, or a transaction signing request. The checkout page looks exactly like a credit card checkout, except the settlement happens on-chain in stablecoins.
Guardrails Built Into the Flow
The hosted action flow includes two safety mechanisms that are important for production deployments.
First, the daily auto-limit is capped at $200 USD per account for agent-initiated payments. This limit applies at the API key level and resets daily. It prevents a compromised key from draining an account. Users who need higher limits can adjust them in the dashboard settings at cai.com/app.
Second, new recipients trigger a confirmation requirement. When the agent tries to send to an address or email that has never received a payment from this account before, the hosted action page shows a prominent warning about the new recipient. The user must explicitly confirm before the payment proceeds. This prevents a common attack pattern where an attacker substitutes their own address in a payment request.
The vault product (credential storage and rotation) covers the case where the agent's API key itself is compromised, but that is a separate track from the payment flow.
What This Enables
The intent-plus-hosted-action pattern is the foundation for several checkout models:
SaaS checkout. A subscription service generates a hosted action URL for each billing cycle. The user confirms once per period. No saved payment methods, no recurring charge disputes, no PCI scope.
Agent-to-agent settlement. Agent A needs to pay Agent B for a completed task. Agent A creates an intent and a hosted action link, which the user confirms. Both agents receive the settlement notification.
One-time storefront purchases. A digital goods storefront generates a hosted action link at checkout. The customer confirms in one tap. The store receives the payment in USDC within seconds.
Each of these models relies on the same three-layer architecture: create the intent, confirm via hosted action, poll until settlement. The abstraction is consistent whether the buyer is a human clicking a link or an agent calling an API.
The Polling Implementation
A practical polling implementation in Python looks like this:
import time, requests
def poll_payment_intent(intent_id, interval=5, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(
f"https://api.cai.com/functions/v1/payment-intent-status?id={intent_id}",
headers={"Authorization": f"Bearer {CAI_API_KEY}"}
)
data = resp.json()
if data.get("status") in ("completed", "failed"):
return data
time.sleep(interval)
raise TimeoutError(f"intent {intent_id} did not settle in {timeout}s")
For production, replace the synchronous loop with an async version or use the webhook path. The synchronous version is fine for low-volume checkout flows where the merchant processes a few hundred payments per day.
Status-Code Handling
Every endpoint in the flow follows the same error contract as the rest of the CAI API. 401 means the API key is invalid or expired. 403 means the key has insufficient scope (checkouts require pay or full scope). 429 means rate limiting. Back off and retry with exponential delay.
The intent-specific error codes include a gap_id field when the requested feature is still partial-live. Treat a gap_id response the same way you treat a 503: the feature exists but has limitations. Check the capabilities page at cai.com/capabilities for the current status of each feature.
Summary
The checkout flow built on payment intents and hosted actions solves a specific problem: how does an agent initiate a payment and get confirmation from a human user without sharing private keys, without asking the user to install a wallet, and without requiring the merchant to hold crypto? The answer is a stateful intent, a single-tap confirmation page, and a polling loop. The settlement happens in USDC on chain, but neither the buyer nor the seller touches the blockchain directly.
Documentation: cai.com/skill.md ยท cai.com/developers.html ยท cai.com/capabilities.html
If You Tried the Checkout Flow and Hit a Bug
Comment below with:
- What you ran the curl command, the create-hosted-action request, the intent id you polled.
- What you expected the checkout to complete in one line, or the intent to transition to completed.
- What you got the error message, the unexpected status, the timeout.
- Your environment OS, Node or Python version, the MCP host (OpenClaw, Hermes, Codex, Cursor, or other), the CAI account tier.
Every comment on this article gets read. Bug reports will be replied to within 24 hours. Friction points shape what we document next.
Top comments (0)