Agent-Native Payments: Idempotency, Webhooks & Settlement
The Silent Killer: Double Charges in Agent Workflows
Imagine your AI customer-support bot just processed a refund. The LLM called your payment API, got a timeout, and—panicking—retried the request. Your customer now sees two refunds. Your accounting team emails at 2 AM. Your payment provider's support queue has you on hold.
This is the idempotency problem. And it's brutal in agent-native systems because AI orchestrators don't think like humans. They retry. They explore. They hallucinate edge cases into reality.
AgentPay VN solves this—and more. This tutorial walks you through building payment flows that are bulletproof: idempotent, webhook-driven, and settled directly to your merchant bank account in minutes, not days.
Why Agent-Native Payments Are Different
Traditional payment integrations assume a human is clicking "Pay Now." The flow is linear, synchronous-ish, and forgiving of the occasional timeout.
AI agents operate differently:
- They run asynchronously. An LLM spawns a payment task, continues reasoning, and checks back later.
- They retry on failure. If the API hiccups, the agent doesn't wait for human intervention—it retries.
- They make decisions autonomously. No human standing behind them to say "stop." Your API must be idempotent.
- They demand transparency. Agents need real-time settlement confirmation to update ledgers, trigger fulfillment, and avoid double-booking.
AgentPay VN's design—with idempotent endpoints, webhook settlement notifications, and direct bank feeds—addresses all four.
Idempotency: The Foundation of Safe Agent Payments
What Is Idempotency?
Idempotency means calling an API 100 times with the same request produces the same result as calling it once. It's a mathematical property, and it's non-negotiable for agent systems.
When you create a payment request with AgentPay VN, you provide an idempotency key—a unique ID for that payment. If your agent retries the same request with the same key, AgentPay returns the original response. No duplicate charge.
Real-World Scenario: Course-Selling Bot
You've built an AI tutoring platform. Students chat with a bot to purchase courses. Here's what happens:
- Student asks: "Buy the advanced Python course for 299,000 VND."
- Agent calls
create_payment_request(idempotency_key="student_42_course_python_001", amount=299000, ...). - AgentPay returns a checkout URL. Network hiccup; agent doesn't see it.
- Agent retries with the same idempotency key.
- AgentPay returns the same checkout URL. No new payment created.
- Student scans QR code once. Pays once. Everyone wins.
Without idempotency keys, retry #2 would create a second payment request, confusing the student and your accounting.
The 3-Line Flow: Create, Send, Settle
AgentPay VN's design is beautifully simple:
Payment Request → Checkout URL → Settlement Confirmation
Let's break it down with code.
Step 1: Create a Payment Request
from agentpay_vn import AgentPayClient
import uuid
# Initialize the client (API key from your AgentPay VN dashboard)
client = AgentPayClient(api_key="your_api_key_here")
# Generate a unique idempotency key for this transaction
idempotency_key = f"order_{uuid.uuid4()}"
# Create a payment request
payment = client.create_payment_request(
idempotency_key=idempotency_key, # ← Critical: prevents duplicate charges
amount=500000, # VND
order_id="course_purchase_001", # Your internal order ID
description="Advanced Python Course",
customer_email="student@example.com",
redirect_url="https://yourapp.com/success" # Where to send user after payment
)
print(f"Checkout URL: {payment['checkout_url']}")
print(f"Payment ID: {payment['payment_id']}")
Line-by-line:
- Line 1-2: Import the SDK (install with pip install agentpay-vn).
- Line 5: Initialize the client with your API key.
- Line 8: Generate a UUID-based idempotency key. This key is your insurance policy. If the agent retries, use the same key.
- Line 11-19: Create the payment request. AgentPay validates the amount, checks your merchant account, and returns a QR code URL.
- idempotency_key: Most critical parameter. Use something deterministic for your order (e.g., database order ID + timestamp).
- amount: In Vietnamese Dong (VND). No currency conversion needed.
- redirect_url: Where AgentPay redirects the user after they scan and pay (optional but recommended).
What AgentPay does NOT do: Hold money. The QR code points directly at your merchant's bank account. The payer's bank contacts your bank. You own the flow.
Step 2: Send Checkout URL to Customer
# Your agent sends this to the customer
checkout_url = payment['checkout_url']
# In a Telegram bot, Discord bot, or web app:
print(f"Pay here: {checkout_url}")
# Or render as QR code
from qrcode import make
qr = make(checkout_url)
qr.save("payment_qr.png")
The customer scans the QR code, authorizes the payment in their banking app, and AgentPay notifies you via webhook.
Step 3: Await Settlement Confirmation
This is where webhooks shine. Instead of polling, AgentPay pushes settlement notifications to your server:
import asyncio
from agentpay_vn import await_settlement
# Your agent waits for payment confirmation (non-blocking)
async def process_payment(payment_id, timeout=300):
"""
Wait for settlement confirmation.
timeout: max seconds to wait (default: 5 minutes)
"""
settlement = await await_settlement(
payment_id=payment_id,
timeout=timeout
)
if settlement['status'] == 'settled':
print(f"✓ Payment confirmed! Amount: {settlement['amount']} VND")
print(f" Merchant received: {settlement['net_amount']} VND")
print(f" Settlement date: {settlement['settled_at']}")
# Now safe to unlock course, send invoice, trigger fulfillment
return True
elif settlement['status'] == 'expired':
print("✗ Payment QR expired. Create a new request.")
return False
# Run async
result = asyncio.run(process_payment(payment['payment_id']))
Key points:
- await_settlement is async-first, so it plays nicely with agent orchestrators (LangChain, AutoGPT, etc.).
- No polling. Your agent doesn't hammer the API every 100ms.
- Real settlement data: You get confirmation from the bank feed, not just a transaction record. Money is actually in your account.
- Timeout: If payment doesn't arrive in 5 minutes, QR expires and user needs to retry.
Webhooks: Push Notifications for Real-Time Confidence
Why Webhooks Matter for Agents
Your agent doesn't want to ask "Is the payment settled?" every 5 seconds. That's wasteful and slow. Instead, AgentPay sends you a webhook the instant settlement is confirmed:
POST /webhook/payment-settled
{
"event": "payment.settled",
"payment_id": "pay_abc123",
"order_id": "course_purchase_001",
"amount": 500000,
"net_amount": 497500,
"settled_at": "2025-01-15T10:30:45Z",
"merchant_bank_account": "1234567890",
"fee": 2500
}
Your server receives this, verifies the signature, and updates your database. Your agent then proceeds with confidence.
Setting Up Webhooks
- In AgentPay VN dashboard: Add your webhook URL (e.g.,
https://yourapp.com/webhooks/agentpay). - Verify signatures: AgentPay signs each webhook with HMAC-SHA256. Always verify before processing.
from flask import Flask, request
import hmac
import hashlib
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_from_dashboard"
@app.route('/webhooks/agentpay', methods=['POST'])
def handle_settlement_webhook():
payload = request.get_data()
signature = request.headers.get('X-AgentPay-Signature')
# Verify signature
expected_sig = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if signature != expected_sig:
return {'error': 'Invalid signature'}, 401
# Safe to process
data = request.get_json()
if data['event'] == 'payment.settled':
order_id = data['order_id']
amount = data['amount']
# Update your database
# Mark order as paid
# Trigger fulfillment (unlock course, send invoice, etc.)
db.orders.update_one(
{'_id': order_id},
{'$set': {'status': 'paid', 'payment_id': data['payment_id']}}
)
return {'status': 'ok'}, 200
Pro tip: Store the payment_id in your database for reconciliation. When your bank feed confirms a deposit, match it to the webhook event.
MCP Server: Native Integration with Claude and AI Agents
If you're using Claude or another LLM-powered system, AgentPay VN provides an MCP (Model Context Protocol) server so your AI agent can call payment functions natively:
pip install agentpay-vn
Then configure your MCP client (e.g., Claude Desktop):
{
"mcpServers": {
"agentpay": {
"command": "python",
"args": ["-m", "agentpay_mcp"],
"env": {
"AGENTPAY_API_KEY": "your_api_key_here"
}
}
}
}
Now Claude can call:
- create_payment_request(amount, order_id, description, ...)
- await_settlement(payment_id)
- get_payment_status(payment_id)
Example agent interaction:
User: "I want to buy the advanced course for 299,000 VND."
Agent (Claude): I'll help you purchase the course.
1. Creating a payment request...
→ Result: Checkout URL generated.
2. Here's your payment QR code: [QR image]
3. Waiting for settlement confirmation...
→ Payment received! Course access granted.
→ Invoice sent to your email.
No human intervention needed. Pure agent autonomy.
Real-World Walkthrough: AI Café Order Bot
Let's build a concrete example: a Telegram bot that sells coffee and collects payments.
Scenario
Customer messages: "1x Cappuccino + 1x Croissant." Bot calculates total (150,000 VND), creates payment request, sends QR, waits for settlement, confirms order.
Code Skeleton
from agentpay_vn import AgentPayClient
from telegram.ext import Application, CommandHandler, MessageHandler, filters
import uuid
import asyncio
client = AgentPayClient(api_key="your_key")
TG_TOKEN = "your_telegram_token"
async def order_handler(update, context):
user_id = update.effective_user.id
order_text = update.message.text
# Parse order (e.g., "1x Cappuccino")
# Calculate total: 80,000 VND
total = 80000
order_id = f"cafe_{user_id}_{uuid.uuid4()}"
# Step 1: Create payment
payment = client.create_payment_request(
idempotency_key=order_id,
amount=total,
order_id=order_id,
description=order_text,
customer_email="" # Telegram bot, no email
)
# Step 2: Send QR to customer
await context.bot.send_photo(
chat_id=update.effective_chat.id,
photo=payment['checkout_url'],
caption=f"Your order: {order_text}\nTotal: {total:,} VND\nScan to pay →"
)
# Step 3: Wait for settlement (non-blocking)
asyncio.create_task(wait_and_confirm(payment['payment_id'], update.effective_chat.id, context))
async def wait_and_confirm(payment_id, chat_id, context):
from agentpay_vn import await_settlement
try:
settlement = await await_settlement(payment_id, timeout=300)
if settlement['status'] == 'settled':
await context.bot.send_message(
chat_id=chat_id,
text=f"✓ Payment confirmed!\nYour order is being prepared.\nPickup in 10 minutes."
)
# Trigger kitchen display, update inventory, etc.
else:
await context.bot.send_message(
chat_id=chat_id,
text="✗ Payment expired. Please try again."
)
except Exception as e:
await context.bot.send_message(
chat_id=chat_id,
text=f"Error: {str(e)}"
)
# Setup bot
app = Application.builder().token(TG_TOKEN).build()
app.add_handler(MessageHandler(filters.TEXT, order_handler))
app.run_polling()
What happens:
1. Customer orders via Telegram.
2. Bot generates unique order_id (idempotency key).
3. AgentPay creates payment request.
4. Customer gets QR code.
5. Customer scans, pays from bank app.
6. Webhook arrives → bot confirms to customer.
7. No double charges. No ambiguity. Café owner is happy.
Do's and Don'ts: Best Practices Checklist
| Do | Don't |
|---|---|
| Use a unique, deterministic idempotency key per order. | Reuse idempotency keys across different orders. |
| Verify webhook signatures before processing. | Trust webhook data without HMAC verification. |
Store payment_id in your database for reconciliation. |
Rely solely on webhook events; add audit logs. |
Use await_settlement for async confirmation. |
Poll the API every 100ms. |
| Set a reasonable timeout (300–600 seconds). | Let QR codes live forever; payment intent expires. |
| Test with small amounts first. | Go live with production code untested. |
Troubleshooting & FAQ
Q1: "My agent is retrying the payment request, and I'm seeing duplicate payment IDs."
A: Ensure you're using the same idempotency_key for retries. AgentPay deduplicates based on the key, not the payment ID. If payment IDs differ, you're creating new requests—use a deterministic key (e.g., order_123_v1).
Q2: "Webhook arrived, but I'm not sure if the payment is in my bank account yet."
A: AgentPay only sends the webhook after your bank feed confirms settlement. The settled_at timestamp is when your bank confirmed the deposit. The money is there.
Q3: "How do I handle QR code expiry?"
A: QR codes expire after 15 minutes (or your configured timeout). If a customer waits too long, await_settlement times out. Send them a message: "Payment request expired. Scan the new QR code below." Regenerate with a new idempotency key.
Q4: "What if my webhook endpoint is down when a payment settles?"
A: AgentPay retries webhook delivery for 24 hours with exponential backoff. Meanwhile, you can always call get_payment_status(payment_id) to check. Build a reconciliation job that runs daily to catch missed events.
Advanced Tips
-
Reconciliation: Daily, fetch unsettled payments and compare to your bank statement. AgentPay's bank feed integration handles this, but log it in your system.
-
Partial Payments: If your product supports "pay what you want," generate a new idempotency key per payment attempt. Each is independent.
-
Refunds: If a customer disputes a payment, AgentPay routes it through your merchant account. You issue refunds via your bank. AgentPay doesn't hold escrow.
-
Multi-Currency (Future): Today, AgentPay VN focuses on VND. For international customers, consider building a layer that converts their currency to VND.
Key Takeaways
- Idempotency is non-negotiable: Use unique keys; retries are safe.
- Webhooks are your friend: Push beats polling. You get real-time, bank-confirmed settlement.
- AgentPay never holds money: QR points to your merchant account. Full transparency.
- MCP server makes agents native: Claude and other LLMs can call payment functions directly.
- The 3-line flow is simple but powerful: Create request → send URL → await settlement.
- Always verify webhook signatures: Protect your fulfillment logic from spoofed events.
Get Started Today
Ready to build agent-native payments?
Installation:
pip install agentpay-vn
Full Docs & API Reference: https://agentpay.servicesai.vn/v1/docs
GitHub (MIT Licensed): https://github.com/phuocdu/agentpay-vn
Next Steps: 1. Grab your API key from the dashboard. 2. Run the quickstart example. 3. Integrate webhooks into your app. 4. Test with a small payment. 5. Deploy with confidence.
Your AI agents can now collect payments reliably, without middlemen, without hold-ups, and without the 2 AM accounting emails. Welcome to agent-native commerce.