Agent-Native Payments: Idempotency, Webhooks & Settlement

2026-07-25 · AgentPay VN

paymentsai-agentsvietqrwebhookspython

The Real Problem: When Your AI Agent Retries and Charges Twice

Imagine you've built a helpful AI chatbot that sells online courses. A customer asks to buy your ₫299,000 "Python Mastery" course. Your agent creates a payment request, generates a VietQR checkout URL, and sends it to the customer. But then—the webhook confirmation doesn't arrive fast enough. Your agent times out. It retries. Now the customer's phone has two identical QR codes, and without idempotency protection, both could settle.

Or worse: a network glitch causes your webhook listener to miss the settlement notification. Your database still shows "payment pending." The customer gets frustrated. Revenue tracking breaks.

These are the real-world headaches that plague AI-driven payment systems. AgentPay VN solves them with a simple, bank-account-direct design—but only if you implement idempotency, webhooks, and settlement confirmation correctly.

Let's build a bulletproof payment flow.

Understanding the AgentPay VN Architecture

AgentPay VN is open-source (MIT) and does one thing exceptionally well: it lets your AI agent collect payments without ever touching the money. Here's how:

  1. Agent creates a payment request → AgentPay generates a unique VietQR code.
  2. QR points straight to the merchant's bank account → No intermediary, no escrow.
  3. Bank settlement feed confirms deposit → You receive a webhook when funds arrive.

This three-line philosophy (create_payment_requestsend checkout_urlawait_settlement) is fundamentally different from traditional payment gateways. There's no token storage, no PCI compliance headache, and no payment processor holding your money.

But that simplicity demands precision from your code. Let's see why.

Idempotency: The Silent Killer of Duplicate Charges

Why Idempotency Matters for Agents

AI agents are retry machines. LLMs hallucinate. Networks flake. Your agent might: - Call create_payment_request() three times while waiting for a response. - Resend a checkout URL because it didn't see the user's confirmation. - Retry settlement checks due to a transient error.

Without idempotency, each retry spawns a new payment request—and a new QR code that could be scanned and paid separately.

How AgentPay VN Implements Idempotency

The SDK uses idempotency keys: you pass a unique string (e.g., user_id + order_id + timestamp_hash) to create_payment_request(). The server remembers that key. If an identical request arrives 30 seconds later, the system returns the same payment request ID and QR code—no duplicate charge possible.

Code Example: Safe Agent Retry Logic

from agentpay_vn import create_payment_request, await_settlement
import hashlib
from datetime import datetime

# Agent flow for a course sale
user_id = "user_12345"
order_id = "order_67890"
course_price_vnd = 299000

# Generate deterministic idempotency key
key_source = f"{user_id}:{order_id}:{course_price_vnd}"
idempotency_key = hashlib.sha256(key_source.encode()).hexdigest()[:16]

print(f"[Agent] Creating payment with idempotency key: {idempotency_key}")

# Create payment request with idempotency
payment = create_payment_request(
    amount_vnd=course_price_vnd,
    description="Python Mastery Course",
    order_id=order_id,
    idempotency_key=idempotency_key  # Critical: prevents duplicates
)

print(f"[Agent] Payment request ID: {payment.id}")
print(f"[Agent] Checkout URL: {payment.checkout_url}")
print(f"[Agent] QR Code: {payment.qr_code}")

# Agent sends checkout URL to customer
# (even if agent retries, same key = same request, no new charge)

# Await settlement with timeout
print("[Agent] Waiting for settlement confirmation...")
try:
    settlement = await_settlement(
        payment_id=payment.id,
        timeout_seconds=300  # 5-minute timeout
    )
    print(f"[Agent] ✓ Settlement confirmed at {settlement.timestamp}")
    print(f"[Agent] Bank reference: {settlement.bank_reference}")
except TimeoutError:
    print("[Agent] Settlement not received within 5 minutes.")
    print(f"[Agent] Customer can retry scanning: {payment.checkout_url}")

Why this works: - Line 16: The idempotency_key is deterministic—same input always produces the same key. - Line 19-25: If your agent crashes and retries, it uses the same key. AgentPay returns the same payment request—no new QR, no duplicate charge. - Line 29-32: await_settlement() blocks until the bank confirms the deposit, with a graceful timeout.

Webhooks: Real-Time Settlement Notifications

The Polling Alternative (Don't Do This)

You could have your agent poll await_settlement() every few seconds. But polling is inefficient: - Wasted API calls. - Higher latency (poll interval = delay before you know payment settled). - Scaling nightmare (1,000 concurrent agents = 1,000 polling loops).

Webhooks: Push Instead of Pull

AgentPay VN sends a webhook POST request to your server the moment the bank confirms the settlement. Your server processes it immediately—no polling, no delay.

Setting Up a Webhook Listener

Here's a FastAPI webhook endpoint:

from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
import hmac
import hashlib

app = FastAPI()

WEBHOOK_SECRET = "your_webhook_secret_from_agentpay_dashboard"  # Store securely!

class SettlementWebhook(BaseModel):
    payment_id: str
    amount_vnd: int
    order_id: str
    bank_reference: str
    settled_at: str
    signature: str  # HMAC-SHA256 signature for verification

@app.post("/webhooks/settlement")
async def handle_settlement_webhook(payload: SettlementWebhook, request: Request):
    """
    Webhook endpoint that AgentPay VN calls when settlement completes.
    Validates signature to ensure the webhook is authentic.
    """

    # 1. Verify webhook signature (prevent spoofing)
    body_str = await request.body()
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode(),
        body_str,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(payload.signature, expected_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # 2. Mark payment as settled in your database
    print(f"✓ Settlement confirmed for order {payload.order_id}")
    print(f"  Amount: ₫{payload.amount_vnd:,}")
    print(f"  Bank ref: {payload.bank_reference}")

    # Update database (pseudocode)
    db.update_order(payload.order_id, status="paid", bank_ref=payload.bank_reference)

    # 3. Trigger fulfillment (send course access, etc.)
    fulfill_order(payload.order_id)

    return {"status": "received", "order_id": payload.order_id}

Critical security note: Always verify the webhook signature. Anyone on the internet could POST to your endpoint and claim a payment settled. The signature proves AgentPay sent it.

Configuring AgentPay MCP for Claude or Other Agents

If you're using Claude or another MCP-compatible AI agent, you can invoke AgentPay directly from your AI's function calls. Here's the MCP server configuration:

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay_mcp"],
      "env": {
        "AGENTPAY_API_KEY": "sk_live_your_api_key_here",
        "AGENTPAY_WEBHOOK_SECRET": "whsec_your_secret",
        "AGENTPAY_MERCHANT_NAME": "My Course Store"
      }
    }
  }
}

Once configured, your agent can: - Call agentpay.create_payment_request() directly. - Let AgentPay handle settlement confirmations via webhooks. - Focus on business logic, not payment plumbing.

Real-World Walkthrough: An Online Course Bot

Let's trace a complete flow for a bot selling ₫299,000 courses:

Step 1: Customer Asks to Buy

Customer: "I want to buy your Python course."

Agent logic:

1. Recognize purchase intent.
2. Generate idempotency_key = sha256("user_456:course_python")[:16]
3. Call create_payment_request(amount_vnd=299000, idempotency_key=...)
4. Receive payment.checkout_url
5. Send to customer: "Scan this QR to pay ₫299,000: [URL]"

Step 2: Customer Scans & Pays

Customer scans the VietQR code → Opens their banking app → Enters PIN → Confirms ₫299,000 transfer to the course store's account.

(AgentPay doesn't touch the money. The bank feeds it directly to your merchant account.)

Step 3: Bank Settlement

Within 2-10 seconds, the bank confirms the transaction. AgentPay's settlement feed detects it and POSTs a webhook to your /webhooks/settlement endpoint.

Step 4: Fulfillment

Your webhook handler: 1. Verifies the signature. 2. Marks the order as "paid" in the database. 3. Sends a confirmation email with course access link. 4. (Optional) Logs the bank reference for reconciliation.

Step 5: Agent Confirms

The agent receives a webhook confirmation and updates the customer: "✓ Payment received! Your course access is ready. Check your email."

Total time: < 15 seconds from scan to confirmation. Zero agent polling. Zero payment processing fees (VietQR is a direct bank transfer).

Common Pitfalls and How to Avoid Them

Pitfall What Goes Wrong Solution
No idempotency key Agent retries → Two QR codes → Two charges Always pass idempotency_key=unique_string
Ignoring webhook signatures Fake webhook spoofs payment → You fulfill for free Verify HMAC-SHA256 signature before processing
Polling instead of webhooks High latency, API waste Use webhook listener; fall back to polling only for reconciliation
Hardcoding webhook secret Secret exposed in GitHub → Attacker spoofs payments Store secret in environment variables or vault
No timeout on await_settlement() Agent blocks forever if webhook fails Always set timeout_seconds=300 (or less)

Advanced: Handling Webhook Failures

What if your webhook server is down when settlement arrives? AgentPay VN retries the webhook up to 5 times over 24 hours. But don't rely solely on webhooks—implement a reconciliation cron job:

from agentpay_vn import get_payment_status
from datetime import datetime, timedelta

def reconcile_pending_payments():
    """
    Daily reconciliation: check if any 'pending' orders have actually settled.
    Catches missed webhooks.
    """
    pending_orders = db.query("SELECT * FROM orders WHERE status = 'pending'")

    for order in pending_orders:
        # Check status with AgentPay
        status = get_payment_status(order.payment_id)

        if status.settled:
            print(f"✓ Order {order.id} settled (missed webhook). Fulfilling now.")
            db.update_order(order.id, status="paid", bank_ref=status.bank_reference)
            fulfill_order(order.id)
        elif datetime.now() - order.created_at > timedelta(hours=1):
            print(f"✗ Order {order.id} unpaid for >1 hour. Send reminder.")
            send_payment_reminder(order.customer_email, order.checkout_url)

Run this daily (e.g., via Celery) to catch edge cases.

FAQ

Q: Does AgentPay VN take a percentage of each payment? A: No. It's open-source MIT. There are no transaction fees. You receive 100% of the payment into your merchant bank account. (Your bank may charge a standard VietQR transfer fee, typically ₫0 for domestic transfers.)

Q: What if the customer doesn't scan the QR within 5 minutes? A: The payment request doesn't expire. The QR remains valid. The customer can scan it later. Your agent's await_settlement() call will timeout after 300 seconds, but that's just the agent's local timeout—the payment request itself is immortal until settled.

Q: Can I use AgentPay VN for subscription/recurring payments? A: Currently, AgentPay VN is designed for one-time payments. Recurring payments would require your agent to generate a new payment request for each cycle (e.g., monthly). We're exploring subscription support; check the GitHub for updates.

Q: How do I test locally without real bank accounts? A: Use the sandbox environment. Set AGENTPAY_ENV=sandbox in your config. Sandbox VietQR codes are test-only and won't debit real accounts.

Key Takeaways

Next Steps

Ready to build agent-native payments?

  1. Install the SDK: pip install agentpay-vn
  2. Read the full docs: https://agentpay.servicesai.vn/v1/docs
  3. Explore the source code: https://github.com/phuocdu/agentpay-vn
  4. Set up your first payment request using the idempotency pattern from this tutorial.
  5. Configure your webhook handler and test with sandbox mode.

Your agents are ready to collect payments safely, instantly, and directly—without you ever touching the funds. That's the AgentPay VN promise.

Get started →

← All posts