Agent-Native Payments: Idempotency, Webhooks & Settlement

2026-08-19 · AgentPay VN

paymentsai-agentsvietqrwebhookspython

The Silent Killer: Payment Uncertainty in AI-Driven Workflows

Imagine this: your AI chatbot just sold a $50 online course to a customer in Ho Chi Minh City. The agent sent a payment link, the customer scanned the QR code, and—nothing. The agent has no idea if the payment went through. Did the webhook fail? Did the customer close the browser? Is the money sitting in limbo somewhere, or already in the merchant's account?

This is the nightmare scenario for AI payment systems. Unlike traditional web apps where you control the entire request–response cycle, AI agents operate asynchronously: they spawn tasks, check back later, and need bulletproof certainty that money actually moved. Without idempotency, webhooks, and proper settlement tracking, you'll ship products that charge customers twice, miss payment confirmations, or leave transactions in an ambiguous state.

AgentPay VN solves this by providing an open-source Python SDK and MCP server that treats payment requests as first-class, idempotent events—meaning your agent can safely retry without creating duplicate charges. Combined with webhook verification and real-time bank settlement confirmation, you get a payment system built for AI reliability, not just convenience.

Let's dig into how to architect payment flows that actually work.

Why Idempotency Is Non-Negotiable for AI Agents

Idempotency is the property that repeating an operation produces the same result as doing it once. For payments, this is critical.

Consider your agent's decision-making loop:

  1. Agent receives payment instruction: "Collect ₫500,000 for subscription."
  2. Agent calls create_payment_request().
  3. Network hiccup or timeout occurs.
  4. Agent doesn't know if the request succeeded or failed, so it retries.
  5. Without idempotency: two payment requests created, customer gets two QR codes, or accidentally pays twice.

AgentPay VN prevents this by assigning each payment request a unique request_id (a UUID you generate). If your agent retries with the same request_id, the system returns the same payment request—not a new one. No duplicates. No confusion.

How it works in practice:

You generate a stable, deterministic request_id based on the transaction details:

import hashlib
from agentpay_vn import AgentPay

# Stable request ID: hash of customer + amount + timestamp
transaction_key = f"{customer_id}_{amount_vnd}_{order_date}"
request_id = hashlib.sha256(transaction_key.encode()).hexdigest()[:16]

client = AgentPay()
payment = client.create_payment_request(
    request_id=request_id,
    amount=500000,  # VND
    description="Premium course access",
    customer_id=customer_id,
    merchant_id="your_merchant_id"
)

print(f"QR Checkout URL: {payment['checkout_url']}")
# Output: https://checkout.agentpay.vn/...

# Agent crashes, retries after 30 seconds with SAME request_id
payment_retry = client.create_payment_request(
    request_id=request_id,  # Identical
    amount=500000,
    description="Premium course access",
    customer_id=customer_id,
    merchant_id="your_merchant_id"
)

# payment_retry['checkout_url'] == payment['checkout_url']
# No duplicate request created. System is idempotent.

Key insight: the request_id is your safety net. Choose it wisely—derive it from business logic (customer + amount + order date) so retries always reference the same logical transaction.

Settlement: Following Money from QR to Bank Account

Unlike payment aggregators that hold your money in a virtual wallet, AgentPay VN is designed for instant merchant settlement. The QR code points directly at your bank account. But how do you know when the money actually arrived?

Answer: bank feed integration and webhook confirmation.

Here's the flow:

  1. Customer scans QR → payment hits the bank's VietQR network.
  2. Settlement occurs → money lands in your merchant bank account (typically 1–5 seconds).
  3. Bank feed notifies AgentPay → your webhook receives a signed confirmation.
  4. Agent continues workflow → issue course access, send invoice, update database.

The critical step for AI agents is monitoring settlement status. AgentPay provides an await_settlement() method that polls or listens for confirmation:

import asyncio
from agentpay_vn import AgentPay

client = AgentPay()

# Create payment request (idempotent)
payment = client.create_payment_request(
    request_id="order_12345_20250115",
    amount=1_200_000,  # 1.2M VND
    description="Monthly subscription fee",
    customer_id="cust_abc123",
    merchant_id="merch_xyz789"
)

checkout_url = payment['checkout_url']
print(f"Send this to customer: {checkout_url}")

# Agent waits for settlement (with timeout)
async def wait_for_payment():
    try:
        settlement = await client.await_settlement(
            request_id="order_12345_20250115",
            timeout_seconds=300  # Wait up to 5 minutes
        )

        if settlement['status'] == 'confirmed':
            print(f"✓ Payment confirmed!")
            print(f"  Amount received: {settlement['amount']} VND")
            print(f"  Settlement ID: {settlement['settlement_id']}")
            print(f"  Timestamp: {settlement['settled_at']}")
            return True
        else:
            print(f"✗ Payment status: {settlement['status']}")
            return False

    except asyncio.TimeoutError:
        print("⚠ Payment not confirmed within 5 minutes. Check webhook.")
        return None

# Run in your agent's async context
result = asyncio.run(wait_for_payment())

if result:
    # Proceed: grant course access, send receipt, etc.
    print("Provisioning user access...")
else:
    # Retry or escalate
    print("Manual review required.")

Key lines explained:

Webhook Configuration: Real-Time Settlement Alerts

Relying solely on await_settlement() polling can be inefficient. For production agents handling dozens of concurrent payments, webhooks are essential: AgentPay pushes settlement confirmations to you, and your agent reacts immediately.

Setting Up Webhooks

  1. Configure endpoint in AgentPay dashboard: - Webhook URL: https://yourserver.com/webhooks/agentpay - Events: payment.settled, payment.failed, payment.expired

  2. Verify webhook signatures (non-negotiable for security):

import hmac
import json
from hashlib import sha256
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_from_dashboard"

@app.route('/webhooks/agentpay', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-AgentPay-Signature')

    # Verify HMAC-SHA256 signature
    expected_sig = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload.encode(),
        sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected_sig):
        return {'error': 'Invalid signature'}, 403

    # Signature verified; process event
    event = json.loads(payload)
    request_id = event['request_id']
    status = event['status']
    settlement_id = event.get('settlement_id')

    if status == 'confirmed':
        print(f"✓ Payment {request_id} confirmed (settlement: {settlement_id})")
        # Trigger agent action: grant access, send receipt, etc.
        trigger_agent_action(request_id, 'grant_access')

    elif status == 'failed':
        print(f"✗ Payment {request_id} failed")
        trigger_agent_action(request_id, 'notify_customer_retry')

    return {'ok': True}, 200

def trigger_agent_action(request_id, action):
    # Push action to your AI agent queue (e.g., Redis, SQS, etc.)
    pass

if __name__ == '__main__':
    app.run(port=5000, ssl_context='adhoc')
  1. MCP Server Integration (for Claude and other AI agents):

If using AgentPay's MCP server, your AI can invoke payment operations natively:

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "env": {
        "AGENTPAY_MERCHANT_ID": "merch_xyz789",
        "AGENTPAY_API_KEY": "pk_live_...",
        "AGENTPAY_WEBHOOK_SECRET": "whsk_..."
      }
    }
  }
}

Once configured, Claude (or your agent) can call:

"I need to collect ₫2,000,000 for a website redesign from customer alice@example.com. Create an idempotent payment request."

The MCP server translates this to:

client.create_payment_request(
    request_id="alice_website_redesign_2000000",
    amount=2_000_000,
    description="Website redesign service",
    customer_id="alice@example.com"
)

Real-World Walkthrough: AI Course Sales Bot

Let's trace a complete flow: an AI bot selling online courses via VietQR.

Scenario: Customer asks the bot, "I want to enroll in 'Advanced Python for AI' (₫500,000)."

Step 1: Agent generates payment request

from agentpay_vn import AgentPay
import hashlib

client = AgentPay()

customer_email = "student@example.vn"
course_id = "python_ai_advanced"
amount = 500_000

# Deterministic request_id (survives retries)
request_id = hashlib.md5(
    f"{customer_email}_{course_id}".encode()
).hexdigest()[:16]

payment = client.create_payment_request(
    request_id=request_id,
    amount=amount,
    description="Course: Advanced Python for AI",
    customer_id=customer_email,
    merchant_id="course_platform_01"
)

checkout_url = payment['checkout_url']
print(f"Agent response: Scan this QR code to pay: {checkout_url}")

Step 2: Customer scans QR, pays via VietQR

Customer's bank app → VietQR network → settlement to course platform's bank in ~3 seconds.

Step 3: Webhook notifies agent

AgentPay's bank feed catches the settlement and POSTs to your webhook:

{
  "event": "payment.settled",
  "request_id": "a1b2c3d4e5f6g7h8",
  "status": "confirmed",
  "amount": 500000,
  "settlement_id": "settle_1234567890",
  "settled_at": "2025-01-15T10:23:45Z"
}

Step 4: Webhook handler provisions access

@app.route('/webhooks/agentpay', methods=['POST'])
def handle_settlement():
    event = json.loads(request.get_data())

    if event['status'] == 'confirmed':
        request_id = event['request_id']
        customer = lookup_customer_by_request_id(request_id)

        # Grant course access
        grant_course_access(
            customer_email=customer['email'],
            course_id=customer['course_id'],
            access_until=datetime.now() + timedelta(days=365)
        )

        # Send receipt
        send_email(
            to=customer['email'],
            subject="Course Access Granted",
            body=f"Access to 'Advanced Python for AI' is live. Settlement ID: {event['settlement_id']}"
        )

        # Log in database
        db.insert('payments', {
            'request_id': request_id,
            'settlement_id': event['settlement_id'],
            'status': 'completed',
            'timestamp': event['settled_at']
        })

    return {'ok': True}, 200

Step 5: Agent confirms completion to customer

Bot: "✓ Payment confirmed! Your course access is active. 
You can now log in at https://courses.example.vn/dashboard.
Receipt sent to student@example.vn."

Total time: ~5 seconds from QR scan to course access.

Do's and Don'ts for Production Payment Agents

Do Don't
Use deterministic request_id based on business logic Generate random UUIDs (breaks idempotency on retries)
Verify webhook signatures with HMAC-SHA256 Trust unsigned webhook payloads
Store settlement_id in your database (proof of payment) Rely solely on polling await_settlement()
Set reasonable timeouts (5–10 minutes max) on settlement waits Block indefinitely waiting for payment confirmation
Log all payment events (request, confirmation, settlement) Skip audit trails
Handle webhook retries gracefully (accept duplicate events) Fail on duplicate webhook POSTs
Test with sandbox mode before production Deploy directly to live merchant accounts

FAQ: Agent-Native Payments

Q: Can my agent handle multiple concurrent payments?

Yes. await_settlement() is async-safe, so you can spawn 100 concurrent waiters without blocking. Each agent instance tracks its own payment requests via request_id.

Q: What if the customer's bank declines the payment?

AgentPay will send a webhook event with status='failed'. Your agent should notify the customer and optionally retry or escalate. The payment request remains idempotent—no duplicate charges.

Q: Does AgentPay hold my money?

No. The QR code points directly to your bank account. Settlement is instantaneous (1–5 seconds). You never depend on AgentPay as a financial intermediary—just as a payment coordinator.

Q: How do I test payments without real money?

AgentPay supports a sandbox environment. Set your API key to pk_test_... and use test customer IDs. Webhooks fire immediately (no actual bank transfer).

Key Takeaways

Next Steps

Ready to build payment-capable AI agents? Here's how to get started:

  1. Install the SDK: bash pip install agentpay-vn

  2. Explore the docs: Visit https://agentpay.servicesai.vn/v1/docs for API reference, MCP server setup, and webhooks guide.

  3. Fork the GitHub repo: https://github.com/phuocdu/agentpay-vn — examples, tests, and MCP server source code.

  4. Start coding: Use the course sales bot walkthrough above as a template for your use case (café, SaaS, freelance services, etc.).

Your AI agents deserve payment systems built for reliability. AgentPay VN gives you idempotency, webhooks, and instant settlement—the foundation of trustworthy, agent-native payments in Vietnam.

Get started →

← All posts