One-Time Pay
The One-Time Pay capability provides a simplified instant payment flow. Unlike Request Payment, which returns a QR code for polling, One-Time Pay returns a deep link that can be opened directly — ideal for chat interfaces, inline payments, and scenarios where the user expects a single tap to complete payment.
Swimlane Diagram — One-Time Pay Lifecycle
The One-Time Pay flow is more compact than Request Payment. The key difference: no QR code generation, no scanning phase, and no separate capture step.
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Human │ │ Agent │ │ ItPay │ │ Payment │
│ (Payer) │ │ (Buyer) │ │ Protocol │ │ Channel │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
│ 1. User taps │ │ │
│ 'Unlock for │ │ │
│ $0.99' │ │ │
├──────────────────▶ │ │
│ │ │ │
│ │ 2. POST /v1/ │ │
│ │ payments/one-time│ │
│ │ {service_id, │ │
│ │ amount, payer} │ │
│ ├──────────────────▶│ │
│ │ │ │
│ │ │ 3. Create deep │
│ │ │ link via channel │
│ │ ├──────────────────▶│
│ │ │ │
│ │ │ 4. Deeplink URI │
│ │ │ ◀────────────────┤
│ │ │ │
│ │ 5. 201 Created │ │
│ │ {id, deeplink, │ │
│ │ status:pending} │ │
│ │ ◀────────────────┤ │
│ │ │ │
│ 6. Show link │ │ │
│ 'itpay://pay/…' │ │ │
│ ◀───────────────┤ │ │
│ │ │ │
│ 7. Human taps │ │ │
│ link → wallet │ │ │
│ opens & pays │ │ │
├─────────────────────────────────────────────────────────▶│
│ │ │ │
│ │ │ 8. Channel │
│ │ │ callback/webhook │
│ │ │ {succeeded} │
│ │ │ ◀────────────────┤
│ │ │ │
│ │ 9. Webhook: │ │
│ │ payment_intent. │ │
│ │ succeeded │ │
│ │ ◀────────────────┤ │
│ │ │ │
│ 10. 'Here's your │ │ │
│ report' │ │ │
│ ◀───────────────┤ │ │
Comparison: Request Payment vs. One-Time Pay
| Aspect | Request Payment | One-Time Pay |
|---|---|---|
| API endpoint | POST /v1/payment-intents | POST /v1/payments/one-time |
| Response artifact | QR code (scan_url) | Deep link (deeplink) |
| User interaction | Scan QR with wallet app | Tap link → wallet opens automatically |
| Polling required | Yes — 3s intervals for full lifecycle | Minimal — just wait for completed |
| Intermediate states | qr_generated, scanning, authorized, captured | pending → completed (direct) |
| Capture step | Required (agent must call /capture) | Automatic (captured on settlement) |
| Best for | Web/mobile checkout, in-person, physical displays | Chat interfaces, inline payments, CLI, auto-pay |
| Multi-channel | Multiple QR codes shown side-by-side | Single deep link per channel (choose ahead) |
| Auto-pay | Not typical (requires scanning) | Full support — if amount ≤ auto_pay_limit, no user action needed |
When to Use Which
- Use Request Payment when: the human is at a computer/phone with a screen, you want to show QR codes, or you need the full lifecycle with authorization and separate capture.
- Use One-Time Pay when: the conversation is in a chat/CLI/messaging interface, you want the fastest path to payment, or you support auto-pay for small amounts.
Endpoint
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/payments/one-time | Create and execute an instant one-time payment |
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
service_id | string (UUIDv7) | yes | The ServiceManifest id |
amount | Money | yes | Amount to charge |
amount.currency | string (ISO 4217) | yes | Currency code |
amount.value | number | yes | Amount in minor units |
description | string | yes | What this payment is for |
payer | Party | yes | Paying agent and optional human |
payer.agent_id | string | yes | Paying agent identifier |
payer.human_id | string | no | Human user identifier |
channel | string | no | Payment channel override (defaults to install preference) |
return_url | string (URL) | no | Redirect URL after payment |
metadata | object | no | Arbitrary key-value data (max 4 KB) |
Money Object
| Field | Type | Description |
|---|---|---|
currency | string (ISO 4217) | Currency code |
value | number | Amount in minor currency units |
Detailed Interaction Trace
This section provides a complete step-by-step trace of every interaction in the One-Time Pay flow.
Step 1: Human Requests Instant Payment
Human-side dialog:
Human: "I need the premium market analysis report for Q2 2026. It says $0.99." Agent: "Would you like to pay $0.99 via Alipay to unlock it now?" Human: "Yes, go ahead."
Agent-side behavior: The agent verifies:
- The human has consented to pay
- The service is installed and
auto_pay_limitis checked - If
auto_pay_limit ≥ 99(in USD minor units), the payment completes without any additional user interaction - Otherwise, the agent prepares the One-Time Pay request
Error: Human declines Agent responds: "Understood. The report is available when you're ready." No API call is made.
Error: Service not installed Agent says: "This service needs to be installed first. Would you like to install it?" See Install for the install flow.
Step 2: Agent Creates One-Time Payment (POST /v1/payments/one-time)
HTTP Request:
POST /v1/payments/one-time
Content-Type: application/json
Authorization: Bearer ag_sk_...Key: 01J7XZ5A6B7C8D9E0F1G2H3I4J
{
"service_id": "01J7XYKZ1A2B3C4D5E6F7G8H9I",
"amount": {
"currency": "USD",
"value": 99
},
"description": "Unlock premium report — Market Analysis Q2 2026",
"payer": {
"agent_id": "agent_cli_a1b2c3d4",
"human_id": "user_abc_789"
},
"channel": "alipay",
"return_url": "https://my-agent.ai/reports/market-q2",
"metadata": {
"report_id": "rpt_market_q2_2026",
"request_id": "req_abc_123"
}
}
HTTP Response (Success):
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: req_01J7XZ6B7C8D9E0F1G2H3I4J5K
{
"id": "pi_01J7Z5E6F7G8H9I0J1K2L3M4N",
"status": "pending",
"amount": {
"currency": "USD",
"value": 99
},
"settlement": {
"currency": "USD",
"value": 99,
"rate": 1.0
},
"description": "Unlock premium report — Market Analysis Q2 2026",
"payer": {
"agent_id": "agent_cli_a1b2c3d4",
"human_id": "user_abc_789"
},
"payee": {
"agent_id": "agent_srv_9x8y7z6w",
"merchant_account": "merchant@alipay"
},
"channel": "alipay",
"deeplink": "itpay://pay/pi_01J7Z5E6F7G8H9I0J1K2L3M4N?amount=99¤cy=USD&channel=alipay",
"expires_at": "2026-05-27T09:15:05Z",
"created_at": "2026-05-27T09:10:00Z",
"metadata": {
"report_id": "rpt_market_q2_2026",
"request_id": "req_abc_123"
}
}
Auto-pay path (if amount ≤ auto_pay_limit):
The response may return status: "completed" immediately with no deeplink:
{
"id": "pi_01J7Z5E6F7G8H9I0J1K2L3M4N",
"status": "completed",
"amount": { "currency": "USD", "value": 99 },
"auto_paid": true,
"succeeded_at": "2026-05-27T09:10:02Z"
}
When auto_paid: true, the agent can immediately deliver the service — no deep link needed.
Error: 400 Bad Request — Validation failure
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "validation_error",
"code": "INVALID_PAYER",
"message": "payer.agent_id is required",
"details": {
"field": "payer.agent_id",
"constraint": "required"
}
}
Handling: Agent logs validation error and surfaces to developer. No automatic retry for validation errors.
Error: 402 Payment Required The endpoint itself participates in the x402 flow. See x402 Semantics.
Error: 409 Conflict — Duplicate idempotency key
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"code": "IDEMPOTENCY_KEY_USED",
"message": "A payment with identical service_id, payer.agent_id, and metadata already exists"
}
Handling: Agent checks if the existing payment completed. If yes, delivers the service. If pending, continues monitoring. If failed, creates a fresh request with new metadata.
Error: 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 3
{
"error": "rate_limited",
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 3 seconds."
}
Handling: Agent waits Retry-After seconds, retries with exponential backoff (max 3 attempts).
Error: 500/502/503 — Server error
HTTP/1.1 503 Service Unavailable
Handling: Exponential backoff retry (1s, 2s, 4s), max 3 attempts. Agent says: "The payment service is temporarily unavailable. Please try again."
Step 3: ItPay Protocol Creates Deep Link
This step happens server-side within the ItPay Protocol.
ItPay internal logic:
- Validates the service exists and
payeris authorized - If
auto_pay_limitis set and amount ≤ limit: skips deep link entirely, processes payment directly - Otherwise: creates a channel-specific deep link
- For Alipay: generates
alipay://URI with the payment details - Packages as an
itpay://deep link
Channel-specific deep link generation (internal):
POST https://openapi.alipay.com/gateway.do
Content-Type: application/x-www-form-urlencoded
method=alipay.trade.create
&app_id=2021001122334455
&charset=utf-8
×tamp=2026-05-27+09%3A10%3A00
&version=1.0
&sign=***RSA2***
&biz_content={
"out_trade_no": "pi_01J7Z5E6F7G8H9I0J1K2L3M4N",
"total_amount": "0.99",
"subject": "Unlock premium report — Market Analysis Q2 2026",
"timeout_express": "5m",
"product_code": "QUICK_MSECURITY_PAY"
}
Error: Channel deep link creation fails
ItPay returns the PaymentIntent with status: "failed" and an error code. Agent surfaces the error and does not retry automatically (user was already asked).
Error: Channel does not support deep links
If payer_channel is set to a channel without deep link support (e.g., only PromptPay QR), the API returns:
{
"error": "channel_error",
"code": "CHANNEL_NO_DEEPLINK",
"message": "The selected channel does not support deep link payments. Use Request Payment instead."
}
Handling: Agent falls back to Request Payment flow: "That channel doesn't support instant payment. Let me create a QR code instead."
Step 4: Agent Presents Deep Link to Human
Human-side dialog:
Agent: "One moment — I've created a payment link for $0.99 via Alipay." Agent: "👉 Tap to pay $0.99 with Alipay" Agent: "This link expires in 5 minutes."
Agent-side behavior:
- Renders the
deeplinkas a clickable link (or button in supported interfaces) - Sets up a short polling interval (every 2 seconds) or registers for webhook
- If the interface doesn't support clickable links (e.g., pure voice), falls back to QR code:
Fallback for voice-only interfaces:
POST /v1/payment-intents
(Redirects to Request Payment flow with QR code)
Step 5: Human Taps Deep Link
Human-side dialog:
Human taps the link on their phone. Alipay app opens automatically with pre-filled payment details. Alipay shows: "Pay $0.99 to Market Analysis Service" Human confirms with fingerprint/FaceID. Alipay shows: "Payment successful!"
Agent-side behavior: The agent is either:
- Polling every 2 seconds:
GET /v1/payment-intents/{id} - Waiting for webhook: the ItPay Protocol sends
payment_intent.succeeded
Polling trace:
GET /v1/payment-intents/pi_01J7Z5E6F7G8H9I0J1K2L3M4N
Authorization: Bearer ag_sk_...ponse:
```http
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "pi_01J7Z5E6F7G8H9I0J1K2L3M4N",
"status": "completed",
"amount": { "currency": "USD", "value": 99 },
"channel": "alipay",
"channel_txn_id": "2026052722009988776655",
"succeeded_at": "2026-05-27T09:10:05Z",
"payer": {
"human_id": "user_abc_789"
},
...
}
Average completion time: 2–5 seconds from deep link tap to status completed.
Error: Human doesn't tap link (timeout)
After expires_at (default 5 minutes), the PaymentIntent transitions to expired:
{
"status": "expired"
}
Agent says: "The payment link has expired. Would you like a new one?" If human agrees, agent creates a new One-Time Pay request.
Error: Payment rejected by human
{
"status": "failed",
"failure_code": "PAYMENT_REJECTED",
"failure_message": "The user cancelled the payment in their wallet"
}
Agent says: "The payment was cancelled. Would you like to try again?" Agent creates a new One-Time Pay request (max 3 retries).
Error: Insufficient balance
{
"status": "failed",
"failure_code": "INSUFFICIENT_BALANCE",
"failure_message": "The wallet does not have sufficient funds"
}
Agent says: "Your wallet doesn't have enough funds. Please top up and try again, or choose a different payment method."
Step 6: Webhook Fires — Payment Succeeded
ItPay Protocol → Agent webhook:
POST https://agent-endpoint.example.com/webhooks/payments
Content-Type: application/json
X-Webhook-Signature: ***HMAC_SHA256***
X-Webhook-Id: wh_01J7XZ7C8D9E0F1G2H3I4J5K6L
X-Webhook-Timestamp: 2026-05-27T09:10:05Z
{
"type": "payment_intent.succeeded",
"data": {
"id": "pi_01J7Z5E6F7G8H9I0J1K2L3M4N",
"service_id": "01J7XYKZ1A2B3C4D5E6F7G8H9I",
"amount": { "currency": "USD", "value": 99 },
"settlement": { "currency": "USD", "value": 99, "rate": 1.0 },
"channel": "alipay",
"channel_txn_id": "2026052722009988776655",
"succeeded_at": "2026-05-27T09:10:05Z",
"auto_paid": false,
"metadata": {
"report_id": "rpt_market_q2_2026",
"request_id": "req_abc_123"
}
}
}
Agent webhook response:
HTTP/1.1 200 OK
Content-Type: application/json
{ "received": true }
Error: Webhook delivery failure (network) ItPay retries delivery up to 5 times over 24 hours with exponential backoff (1min, 5min, 30min, 2h, 6h).
Error: Agent returns non-200 ItPay retries per the schedule above. Agent should always return 200 for webhooks it receives, even if processing fails — process failures asynchronously.
Step 7: Service Delivered
Human-side dialog:
Agent: "Payment received! Here's the Market Analysis Q2 2026 report:" (Agent delivers the report content or link) Agent: "Is there anything else you'd like?"
Agent-side behavior:
- Looks up
metadata.report_idto find the report content - Retrieves the report from the service backend
- Delivers it to the human
- Sends a receipt confirmation (optional)
Error: Service execution fails after payment Agent refunds automatically via Refund with Revocation:
POST /v1/payment-intents/pi_01J7Z5E6F7G8H9I0J1K2L3M4N/refund
Authorization: Bearer ag_sk_...
{
"reason": "service_execution_failure",
"amount": { "currency": "USD", "value": 99 }
}
Agent says: "I'm sorry, the report could not be generated. I've issued a full refund of $0.99."
Deep Link Handling
The deeplink response field contains a URI that the agent or client can open directly.
| Platform | How to Handle |
|---|---|
| Mobile (iOS/Android) | Open the itpay:// URI — the wallet app handles the rest |
| Desktop / Web | Render the deeplink as a QR code or redirect to a web checkout |
| Chat / CLI | Display the deeplink as a clickable link the user can tap |
Deeplink URI Format
itpay://pay/{payment_intent_id}?amount={minor_units}¤cy={ISO_4217}&channel={channel_name}
Example:
itpay://pay/pi_01J7Z5E6F7G8H9I0J1K2L3M4N?amount=99¤cy=USD&channel=alipay
Polling for Completion
Use the PaymentIntent id to poll for status changes:
GET /v1/payment-intents/pi_01J7Z5E6F7G8H9I0J1K2L3M4N
Authorization: Bearer ag_sk_...
Recommended polling interval: 2 seconds (faster than Request Payment since the lifecycle is simpler).
Simplified State Machine
The One-Time Pay flow uses a simplified two-state machine:
┌──────────┐
│ pending │
└────┬─────┘
│
┌────┴─────┐
│ │
▼ ▼
┌────────┐ ┌────────┐
│completed│ │ failed │
└────────┘ └────────┘
Any state ──expires_at reached──▶ expired
Any state ──manual cancel───────▶ cancelled
Transitions
| From | To | Trigger |
|---|---|---|
pending | completed | Payment confirmed on the payment channel |
pending | failed | Payment rejected or channel error |
| any | expired | expires_at timestamp is reached |
| any | cancelled | Intent was manually cancelled |
Error States
| State | Meaning | Recovery |
|---|---|---|
failed | Payment rejected by user, channel, or insufficient funds | Create new One-Time Pay (max 3 retries) |
expired | Deep link lifetime exceeded | Create new One-Time Pay |
cancelled | Manually cancelled | Create new One-Time Pay if needed |
Auto-Pay Flow
When the human's install has auto_pay_limit configured and the amount is at or below that limit, the entire flow collapses to zero user interaction:
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Human │ │ Agent │ │ ItPay │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. 'Show me the │ │
│ report' │ │
├──────────────────▶ │
│ │ │
│ │ 2. POST /v1/ │
│ │ payments/one-time│
│ │ (auto_pay: true) │
│ ├──────────────────▶│
│ │ │
│ │ 3. Auto-debit │
│ │ via stored │
│ │ credentials │
│ │ │
│ 4. 'Here's your │ │
│ report' │ │
│ ◀───────────────┤ │
Human sees: No payment prompt at all — the service is delivered immediately.
Agent checks: auto_pay_limit stored on the Install object. If amount ≤ limit, the POST /v1/payments/one-time returns status: completed and auto_paid: true.
See x402 Semantics for full auto-pay configuration.
Retry Logic Summary
| Failure Point | Retry Strategy | Max Attempts | Human Notification |
|---|---|---|---|
| API 401 (auth) | Rotate key, retry | 1 | "Connection issue, please try later" |
| API 409 (duplicate) | Check existing, reuse if completed | 1 | Silent |
| API 429 (rate-limit) | Wait Retry-After, backoff | 3 | Silent retry |
| API 5xx (server error) | Exponential backoff (1s, 2s, 4s) | 3 | After all fail |
| Deep link expired | New One-Time Pay request | 3 | "Link expired, want a new one?" |
| Payment rejected | New One-Time Pay request | 3 | "Payment cancelled, try again?" |
| Insufficient balance | Suggest top-up or alternative method | 1 | "Please top up or use different method" |
| Webhook delivery | Exponential backoff (1m, 5m, 30m, 2h, 6h) | 5 | N/A (server-side) |
| Service delivery failure | Refund + apologize | 1 | "Full refund initiated" |
Natural Language Examples
"Pay $0.99 to unlock the premium report from Smart Summary."
"Send 99 cents via Alipay for the Market Analysis Q2 report."
"I'd like to instantly pay for this service — here's my wallet."
Agent auto-response (auto-pay): "You're within your auto-pay limit. Charging $0.99 and delivering the report now."
Key Behaviors
- No QR polling: Unlike Request Payment, One-Time Pay returns a
deeplinkrather than a QR code. The client opens the link and the wallet app handles the rest. - Simplified lifecycle: Only
pending,completed,failed,expired, andcancelledstates — noqr_generated,scanning,authorized, orcapturedintermediate states. - Auto-pay compatible: If the install has
auto_pay_limitconfigured and the amount is at or below the limit, the payment completes without any user interaction. See x402 Semantics. - Idempotency: The same request with the same
service_id,payer.agent_id, andmetadataproduces the same result.
Webhook Events
| Event | Description |
|---|---|
payment_intent.succeeded | One-time payment completed |
payment_intent.failed | One-time payment failed |
payment_intent.expired | Payment expired before completion |
Next Steps
- Subscribe Pay — Set up recurring payments
- Refund — Refund a one-time payment
- x402 Semantics — Auto-pay and HTTP 402 flow
- Escrow Payments — For high-value or trust-sensitive payments