# PayAI Hosted Checkout > Stripe Checkout for stablecoins. Create a checkout session with a Stripe-compatible API or the MCP server, send the buyer its url, and get paid in USDC, USDT or USDG. People pay on a hosted page. AI agents pay the same url with x402 (HTTP 402). The money goes straight to the merchant wallet. PayAI never holds funds. - Base URL: https://staging.checkout.payai.network - Amounts are integers in USD cents: 2500 is $25.00. Buyers pay 1 token per 1 USD. - Auth: `Authorization: Bearer payai_ck_live_...` (live) or `payai_ck_test_...` (test mode on testnets). curl `-u payai_ck_...:` also works. - Request bodies can be JSON or Stripe form encoding (`line_items[0][price_data][unit_amount]=2500`). - Paths, parameters, objects and errors follow Stripe Checkout Sessions. ## Docs - [Developer docs](https://staging.checkout.payai.network/docs): Guides for merchants, buyer agents and merchant agents. - [OpenAPI 3.1 document](https://staging.checkout.payai.network/openapi.json): Every endpoint, parameter, object and error. - [MCP server](https://staging.checkout.payai.network/mcp): Tools for merchant agents. Streamable HTTP. Send your checkout key. - [Facilitator authentication](https://docs.payai.network/x402/facilitators/authentication): How to sign the JWT for agent sign-up. ## Merchant quick start 1. Get a checkout key. Sign in at https://staging.checkout.payai.network/dashboard/developers and create a key. An agent can get a key without a browser: see "Agent sign-up". 2. Create a checkout session: ```sh curl https://staging.checkout.payai.network/v1/checkout/sessions \ -u payai_ck_test_YOUR_KEY: \ -H "Idempotency-Key: order-1001" \ -d "line_items[0][price_data][currency]=usd" \ -d "line_items[0][price_data][unit_amount]=2500" \ -d "line_items[0][price_data][product_data][name]=T-shirt" \ -d "line_items[0][quantity]=1" \ -d "client_reference_id=order-1001" \ -d "success_url=https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}" ``` The response is a checkout.session object with `id` (cs_test_...) and `url`. 3. Send the `url` to the buyer. A person opens it in a browser and pays from a wallet. An AI agent pays the same url with x402. To confirm payment, wait for the `checkout.session.completed` webhook or poll `GET /v1/checkout/sessions/{id}` until `status` is `complete`. Set a payout wallet before you create sessions: https://staging.checkout.payai.network/dashboard/settings. Payouts go only to wallets that you signed with. Each payment uses settle credits from your PayAI account. If the account has no credits, buyers cannot pay. Buy credits at https://staging.merchant.payai.network/dashboard/buy-credits. ## Agent sign-up An agent can get a checkout key with no person and no browser. 1. Get a PayAI facilitator key (`payai_sk_...`). Create one in the merchant portal (https://staging.merchant.payai.network), or pay for one with x402 at `POST https://staging.merchant.payai.network/api/v1/keys/vend`. The wallet that pays for a vended key becomes the payout wallet. 2. Sign a short JWT with the facilitator key. Format: EdDSA. Header `{ "alg": "EdDSA", "typ": "JWT", "kid": "" }`. Claims `{ "sub": "", "iss": "payai-merchant", "iat": , "exp": }`. The lifetime must be 10 minutes or less. Details: https://docs.payai.network/x402/facilitators/authentication 3. Call `POST https://staging.checkout.payai.network/v1/keys` with `Authorization: Bearer ` and the body `{ "livemode": false, "name": "My agent" }`. livemode defaults to true. 4. Store `secret` (`payai_ck_test_...` or `payai_ck_live_...`) from the response. You cannot see it again. The response also has `payout_wallets`. Do not send the `payai_sk_` key itself. Each account can create 10 keys per hour. ## API endpoints - `POST /v1/checkout/sessions`: Create a checkout session. Send the returned url to the buyer. - `GET /v1/checkout/sessions`: List checkout sessions, newest first. Query: limit (1 to 100, default 10), starting_after, ending_before, status (open, complete or expired). - `GET /v1/checkout/sessions/{id}`: Get one checkout session. Query: expand[]=line_items. - `POST /v1/checkout/sessions/{id}/expire`: Expire an open session. Nobody can pay it after this. - `GET /v1/checkout/sessions/{id}/line_items`: List the line items of a session (one page). - `GET /v1/payments`: PayAI extension. List succeeded payments, newest first. Query: limit, starting_after. - `POST /v1/webhook_endpoints`: Create a webhook endpoint. Body: url, enabled_events[], description. The response has the signing secret (whsec_...) one time only. - `GET /v1/webhook_endpoints`: List webhook endpoints. - `GET /v1/webhook_endpoints/{id}`: Get one webhook endpoint. - `DELETE /v1/webhook_endpoints/{id}`: Delete a webhook endpoint. - `POST /v1/keys` (auth: Facilitator JWT): Agent sign-up. Exchange a facilitator JWT for a checkout key. Body: livemode (default true), name. - `GET /c/{id}` (auth: none, pay with x402): The session url. Browsers get the payment page. Other clients get x402: HTTP 402 with PAYMENT-REQUIRED. Create parameters (`POST /v1/checkout/sessions`): - `line_items`: Required. 1 to 100 items. - `line_items[].price_data.currency`: usd. Only usd is available. - `line_items[].price_data.unit_amount`: Price of one unit in USD cents (integer). 2500 is $25.00. You can send unit_amount_decimal (whole cents, as a string) instead. - `line_items[].price_data.product_data.name`: Required. Product name, up to 250 characters. - `line_items[].price_data.product_data.description`: Optional. Up to 500 characters. - `line_items[].price_data.product_data.images[]`: Optional. Up to 8 https URLs. - `line_items[].quantity`: Optional. 1 to 999999. Default 1. - `mode`: Optional. payment is the default and the only value. - `success_url`: Optional. The page for the buyer after payment. {CHECKOUT_SESSION_ID} becomes the session id. - `cancel_url`: Optional. The page for a buyer who cancels. - `client_reference_id`: Optional. Your order id, up to 200 characters. - `customer_email`: Optional. The buyer email, for your records. - `metadata`: Optional. Up to 50 keys. Keys up to 40 characters. Values up to 500 characters. - `expires_at`: Optional. Unix time in seconds, 30 minutes to 24 hours from now. Default: 24 hours. - `payment_method_types`: Optional. Only ["crypto"]. - `expand[]`: Optional. line_items adds the items to the response. Send the `Idempotency-Key` header on create. A retry with the same key and body returns the first session. Checkout session statuses: - `open`: Waiting for payment. The url works. - `processing`: A buyer signed a payment and the network is confirming it. Check again in a few seconds. - `complete`: Paid. payment_status is paid, and payment has the token, network and transaction. - `expired`: Nobody can pay it. The url is null. A paid session has a `payment` object (PayAI extension) with `token`, `token_amount`, `network` (CAIP-2), `network_name`, `payer`, `pay_to`, `transaction` and `transaction_url`. Lists use Stripe cursors: `{ "object": "list", "data": [...], "has_more": true }`. For the next page, send `starting_after=`. ## Webhooks 1. Create an endpoint: `POST /v1/webhook_endpoints` with `url` and `enabled_events[]` (`checkout.session.completed`, `checkout.session.expired` or `*`). Store the `secret` (`whsec_...`) from the response. You cannot see it again. 2. PayAI sends a POST with a JSON event: `{ "id": "evt_...", "object": "event", "api_version": "2026-10-15", "created", "livemode", "type", "data": { "object": } }`. 3. Verify the `PayAI-Signature` header. Its format is `t=,v1=`: - Read the raw request body. Do not parse it first. - Compute HMAC-SHA256 of `.` with the whsec_ secret as the key. Encode it as hex. - Compare it with each v1 value in constant time. - Reject the event if t is more than 5 minutes from your clock. 4. Answer with any 2xx status quickly. Do the slow work after you answer. If your endpoint does not answer 2xx in 10 seconds, PayAI tries again with backoff (1 minute, 5 minutes, 30 minutes, then up to 24 hours) for about 3 days. Redirects count as failures. The same event can arrive more than once: skip event ids that you already processed. Stripe libraries can check the signature: give them the PayAI-Signature value in place of Stripe-Signature. Live endpoints must use https on a public host. Test endpoints can use http. Each mode can have 16 endpoints. ## Buyer agents (x402) The session url is also an x402 v2 resource. An agent can pay it with no browser and no PayAI-specific code. 1. Send `GET https://staging.checkout.payai.network/c/{id}` without `Accept: text/html`. The answer is HTTP 402 with the header `PAYMENT-REQUIRED`: base64 JSON `{ x402Version: 2, resource, accepts: [ { scheme: "exact", network, amount, asset, payTo, maxTimeoutSeconds, extra } ] }`. The body has the same JSON. 2. Choose one entry in `accepts` on a network where your wallet has the token. `amount` is in the token's smallest unit (6 decimals: 25000000 is $25.00). `network` is CAIP-2. 3. Sign the payment for that entry. On EVM, sign an EIP-3009 transferWithAuthorization (extra has the EIP-712 name and version). On Solana, sign the transfer transaction with extra.feePayer as the fee payer. 4. Send the request again with the header `PAYMENT-SIGNATURE`: base64 JSON of the payment payload. `X-PAYMENT` also works. Answers: - 200: Paid. The body is `{ "status": "complete", "session": {...} }`. The `PAYMENT-RESPONSE` header is base64 JSON `{ success, transaction, network, payer }`. A GET without a payment header also answers 200 after payment. - 202: The network has not confirmed yet. Send GET without a payment header every 5 seconds until you get 200. - 400: The payment header is not base64 JSON of an x402 payment payload. - 402 after a signed payment: The payment failed and no money moved. The body has `error`, `code` and new `accepts`. You can sign again. If `error` says that the merchant cannot take payments, do not try again. - 404: No session has this id. - 409: Another payment for this session is in progress, the session is already paid, or the signature was used before. - 410: The session expired. Ask the merchant for a new link. - 429: Too many requests. Wait for the Retry-After seconds. - 503: The checkout cannot take payments now, or the payment service is busy. No money moved. Try again later. Standard x402 clients work: `@x402/fetch` (wrapFetchWithPayment or wrapFetchWithPaymentFromConfig), `x402-axios`, and the Python `x402` client. The PayAI facilitator pays the gas. The buyer needs only the token, no native coin. The url allows cross-origin requests (CORS), so an agent in a browser can call it. ## MCP server (merchant agents) - URL: `https://staging.checkout.payai.network/mcp` (MCP Streamable HTTP, stateless, JSON responses). Protocol versions: 2025-06-18, 2025-03-26, 2024-11-05. - Auth: send your checkout key on every request: `Authorization: Bearer payai_ck_...`. A test key works on testnets only. ```json { "mcpServers": { "payai-checkout": { "url": "https://staging.checkout.payai.network/mcp", "headers": { "Authorization": "Bearer payai_ck_live_YOUR_KEY" } } } } ``` Claude Code: ```sh claude mcp add --transport http payai-checkout https://staging.checkout.payai.network/mcp \ --header "Authorization: Bearer payai_ck_live_YOUR_KEY" ``` Tools: - `create_checkout_session`: Make a payment link. Amounts are USD cents. Returns the session with its url. - `get_checkout_session`: Get a session by id. Use it to see if the buyer paid (status complete). - `list_checkout_sessions`: List sessions, newest first. Filter by status. - `expire_checkout_session`: Cancel an open payment link. - `list_payments`: List succeeded payments with token, network and transaction. Tool amounts are integers in USD cents. A tool error comes back as a result with `isError: true` and a message that says what to change. ## Errors Errors use the Stripe shape: `{ "error": { "type", "code", "message", "param", "doc_url", "request_id" } }`. Types: `invalid_request_error` (400, 404, 429),`authentication_error` (401), `idempotency_error` (400), `api_error` (500). Every response has a `Request-Id` header. - `parameter_missing`: A required parameter is missing. param names it. - `parameter_invalid, parameter_invalid_integer, parameter_invalid_string`: A parameter has a bad value. param names it. - `parameter_unknown`: The API does not know this parameter. - `amount_invalid`: The total must be 1 to 99999999 cents. - `currency_unsupported`: Use usd. - `url_invalid, email_invalid`: Fix the URL or the email address. - `payout_wallet_missing`: Set a payout wallet in the dashboard settings before you create sessions. - `resource_missing`: No object has this id in this mode (HTTP 404). - `session_not_open`: You can expire only open sessions. - `idempotency_key_in_use`: You used this Idempotency-Key with different parameters. Use a new key. - `api_key_invalid`: The key is missing, wrong or revoked (HTTP 401). - `rate_limited`: Too many requests (HTTP 429). Wait and try again. - `internal_error`: A problem on our side (HTTP 500). Try again. Give us the request_id if it continues. ## Test mode - A test key (`payai_ck_test_...`) makes test sessions (`cs_test_...`) on testnets. Test tokens have no value. - Test mode data, webhooks and keys are separate from live mode. - Get test USDC from https://faucet.circle.com. - Agent sign-up: send `{ "livemode": false }` to `POST /v1/keys`. ## Tokens and networks Live: - Base (`eip155:8453`): USDC - Arbitrum (`eip155:42161`): USDC, USDT (USDT0), USDG (when enabled) - Polygon (`eip155:137`): USDC - Avalanche (`eip155:43114`): USDC - Sei (`eip155:1329`): USDC, USDT (USDT0) - X Layer (`eip155:196`): USDT (USDT0), USDG (when enabled) - Solana (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`): USDC, USDT, USDG (when enabled) Test: - Base Sepolia (`eip155:84532`): USDC - Arbitrum Sepolia (`eip155:421614`): USDC - Solana Devnet (`solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`): USDC USDT on EVM networks is USDT0. The facilitator pays the gas on every network. Each checkout offers only the networks where the merchant has a payout wallet. On Solana, the payout wallet must have a token account for the token. ## Non-custodial - PayAI never holds funds and never has keys that can move them. - The buyer signs a transfer straight to the merchant payout wallet. The amount, token and payout address are fixed when the buyer signs. - The PayAI facilitator only submits the signed transfer and pays the gas. - Each payment uses settle credits from the merchant's PayAI account, like any facilitator settle. - Payouts go only to wallets that the merchant proved control of with a signature. ## Optional - [Stripe Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions): The API uses the same shapes. - [x402](https://www.x402.org): The open standard for HTTP 402 payments. - [Model Context Protocol](https://modelcontextprotocol.io): The protocol of the MCP server.