Agent-Native Payments: Idempotency, Webhooks & Settlement

2026-09-04 · AgentPay VN

paymentsai-agentsvietqrwebhookspython

The Problem: Why AI Agents Fail at Collecting Money

You've built a brilliant AI agent—maybe it books appointments, sells online courses, or manages a small café's orders. Your customers love it. Then payment time arrives, and everything breaks.

The agent sends a payment request, the network hiccups, and the agent re-sends it. Suddenly the customer sees two invoices. Or the agent crashes mid-transaction and loses track of what was confirmed. Or worse: the money arrives in some intermediary's account instead of straight to your bank.

This is the harsh reality of most payment integrations with AI agents. They're built for stateless, fast transactions—not the messy, unreliable world of real money.

AgentPay VN solves this. It's an open-source Python SDK + MCP server that lets your AI agents collect VietQR payments reliably, with idempotency baked in, webhooks to track settlement, and money that flows directly to your merchant bank account. No middleman. No lost transactions.

Understanding the Three Pillars: Idempotency, Webhooks, Settlement

Before we code, let's establish what "agent-native" payments actually mean.

Idempotency: The Shield Against Retries

AI agents are retry machines. When they encounter a timeout or error, they try again—sometimes multiple times before giving up. Without idempotency, each retry becomes a new transaction.

Idempotency means: the same request, sent twice, produces the same outcome once. AgentPay VN enforces this through an idempotency_key—a unique string you provide that locks in the result of the first attempt.

Webhooks: Real-Time Settlement Confirmation

Your agent creates a payment request and sends the checkout URL to the customer. But when is the money actually in your account? Without webhooks, you're polling the API constantly or keeping transactions in a zombie state.

Webhooks flip this model: the moment VietQR confirms settlement (via your merchant's bank feed), AgentPay VN POSTs to your endpoint. No polling. No lag. Your agent knows immediately.

Settlement: Money in Your Bank, Not a Wallet

Unlike Stripe, PayPal, or momo—where funds land in a hosted wallet first—AgentPay VN is a pass-through. The QR code points at your merchant's bank account. Settlement happens directly. Your agent tracks it via the bank feed confirmation.

Installation and Setup

Start with a clean Python environment (3.8+):

pip install agentpay-vn

If you're using Claude or another AI agent with MCP support, also install the MCP server:

pip install agentpay-mcp

Then configure your merchant details. Create a .env file:

AGENTPAY_MERCHANT_ID=your_merchant_id
AGENTPAY_MERCHANT_NAME=Your Shop Name
AGENTPAY_BANK_ACCOUNT=0123456789
AGENTPAY_BANK_NAME=VCB  # or ACB, MB, etc.
AGENTPAY_WEBHOOK_URL=https://yourserver.com/webhook/agentpay

The AGENTPAY_WEBHOOK_URL is critical—this is where AgentPay VN will POST settlement confirmations.

The Core Flow: Create → Send → Await

AgentPay VN distills payment collection into three steps:

from agentpay_vn import AgentPay, PaymentRequest
import os
from uuid import uuid4

# Initialize with your merchant credentials
agent_pay = AgentPay(
    merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
    merchant_name=os.getenv("AGENTPAY_MERCHANT_NAME"),
    bank_account=os.getenv("AGENTPAY_BANK_ACCOUNT"),
    bank_name=os.getenv("AGENTPAY_BANK_NAME"),
)

# Step 1: Create a payment request with idempotency
idempotency_key = str(uuid4())  # Unique per transaction
payment = agent_pay.create_payment_request(
    amount=450_000,  # VND
    description="Online Course: AI Agents 101",
    customer_name="Nguyễn Văn A",
    customer_phone="0901234567",
    idempotency_key=idempotency_key,  # THIS is the magic
    metadata={"course_id": "ai101", "student_id": "S001"}
)

print(f"Payment ID: {payment.id}")
print(f"Checkout URL: {payment.checkout_url}")
print(f"Status: {payment.status}")

# Step 2: Send checkout URL to customer (via agent message)
agent_message = f"Please complete payment here: {payment.checkout_url}"

# Step 3: Wait for settlement (with timeout)
try:
    settlement = agent_pay.await_settlement(
        payment_id=payment.id,
        timeout_seconds=3600  # Wait up to 1 hour
    )
    print(f"✓ Settled! Reference: {settlement.bank_reference}")
    print(f"Amount confirmed: {settlement.amount_received} VND")
except TimeoutError:
    print("⚠ Customer hasn't paid yet. Agent can retry or remind.")

Line-by-line breakdown:

Idempotency in Production: The Details

Idempotency is not just a nice-to-have—it's essential for agent-native payments because agents will retry.

How to Generate Idempotency Keys

Each payment request needs a unique key. Common patterns:

from uuid import uuid4
from hashlib import sha256

# Pattern 1: UUID (simple, always unique)
key_1 = str(uuid4())

# Pattern 2: Hash of order details (reproducible)
order_hash = sha256(
    f"{customer_id}_{product_id}_{amount}_{timestamp}".encode()
).hexdigest()[:16]

# Pattern 3: Composite (recommended for agents)
key_3 = f"agent_{agent_id}_{order_id}_{uuid4()}"

Choose one pattern and stick with it per transaction. The SDK rejects duplicate keys with the same amount; conflicting amounts return an error.

What Idempotency Prevents

Scenario Without Idempotency With Idempotency
Agent retries on timeout Two payment requests created Same request returned (cached)
Network duplicates packet Multiple charges to customer Duplicate rejected
Agent crashes and restarts Transaction lost / orphaned Agent can re-query and recover
User submits form twice Double-charge Second submit returns existing payment ID

Webhooks: Real-Time Settlement Notifications

Instead of polling, let AgentPay VN push settlement events to you.

Setting Up Your Webhook Endpoint

Create a Flask (or FastAPI) endpoint:

from flask import Flask, request, jsonify
import hmac
import hashlib
import os
from datetime import datetime

app = Flask(__name__)
WEBHOOK_SECRET = os.getenv("AGENTPAY_WEBHOOK_SECRET")

@app.route("/webhook/agentpay", methods=["POST"])
def handle_settlement():
    # Verify signature
    signature = request.headers.get("X-AgentPay-Signature")
    payload = request.get_data()

    expected_sig = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected_sig):
        return jsonify({"error": "Invalid signature"}), 401

    data = request.json

    # Handle settlement event
    if data["event"] == "payment.settled":
        payment_id = data["payment_id"]
        amount = data["amount"]
        bank_ref = data["bank_reference"]
        timestamp = data["settled_at"]

        # Update your database
        print(f"✓ Payment {payment_id} settled for {amount} VND")
        print(f"  Bank ref: {bank_ref}")
        print(f"  Time: {timestamp}")

        # Trigger agent follow-up (e.g., enroll user, send receipt)
        # agent.send_receipt(payment_id, customer_email)

        return jsonify({"status": "ok"}), 200

    return jsonify({"status": "unknown_event"}), 400

if __name__ == "__main__":
    app.run(port=5000)

Key points:

MCP Server Configuration for Claude

If you're running AgentPay VN with Claude or another MCP-compatible agent:

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay_mcp.server"],
      "env": {
        "AGENTPAY_MERCHANT_ID": "your_merchant_id",
        "AGENTPAY_MERCHANT_NAME": "Your Shop",
        "AGENTPAY_BANK_ACCOUNT": "0123456789",
        "AGENTPAY_BANK_NAME": "VCB",
        "AGENTPAY_WEBHOOK_URL": "https://yourserver.com/webhook/agentpay"
      }
    }
  }
}

With this configuration, Claude can natively call create_payment_request, await_settlement, and get_payment_status without you writing integration code.

Real-World Example: AI Course Sales Bot

Let's build a concrete scenario: an AI agent that sells online courses via VietQR.

The flow:

  1. Customer asks the agent about a course.
  2. Agent creates a payment request with the course price (e.g., 450,000 VND for "AI Agents 101").
  3. Agent sends the checkout URL in a chat message.
  4. Customer scans the QR code with their banking app and confirms payment.
  5. The bank processes the transfer to your merchant account.
  6. Webhook fires with settlement confirmation.
  7. Agent receives webhook event, enrolls the student, and sends a thank-you message with course access link.

Agent prompt example:

You are a course advisor AI. When a student wants to enroll, you:
1. Create a payment request via AgentPay (idempotency_key = student_id + timestamp)
2. Share the checkout URL
3. After settlement confirmation (wait up to 1 hour), grant access
4. Send course materials via email

Always handle payment failures gracefully—suggest alternatives or a retry.

Database schema (for tracking):

# Pseudo-code for your database
class Enrollment(Base):
    student_id: str
    course_id: str
    agentpay_payment_id: str  # Link to payment
    idempotency_key: str  # For safety
    status: str  # pending, settled, failed
    amount_vnd: int
    bank_reference: str | None  # Populated on settlement
    created_at: datetime
    settled_at: datetime | None

When the webhook fires, update status = "settled" and bank_reference in this table. Your agent can then query it and proceed.

Advanced Tips: Production Reliability

Retry Logic with Exponential Backoff

import time
from agentpay_vn.errors import AgentPayError

def create_payment_with_retries(agent_pay, amount, description, idempotency_key, max_retries=3):
    for attempt in range(max_retries):
        try:
            return agent_pay.create_payment_request(
                amount=amount,
                description=description,
                idempotency_key=idempotency_key
            )
        except AgentPayError as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # 1s, 2s, 4s
                print(f"Retry in {wait_time}s...")
                time.sleep(wait_time)
            else:
                raise

Monitoring Settlement Lag

Track how long settlement takes from QR scan to bank confirmation:

from datetime import datetime

payment = agent_pay.create_payment_request(...)
created_at = datetime.utcnow()

settlement = agent_pay.await_settlement(payment.id, timeout_seconds=3600)
settled_at = datetime.utcnow()

lag_seconds = (settled_at - created_at).total_seconds()
print(f"Settlement lag: {lag_seconds / 60:.1f} minutes")

VietQR typically settles within 5–30 minutes depending on the banks involved.

Database Reconciliation

Every hour, reconcile your payment records with AgentPay VN:

payments = agent_pay.list_payments(limit=100, status="settled")

for p in payments:
    # Check if already in your DB
    existing = db.query(Enrollment).filter_by(agentpay_payment_id=p.id).first()
    if not existing:
        print(f"WARNING: Settlement {p.id} not in DB—possible webhook failure")
        # Re-process manually or alert ops

Common Mistakes and How to Avoid Them

❌ Don't ✅ Do
Regenerate idempotency key on retry Store and reuse the same key per transaction
Ignore webhook signature verification Always validate X-AgentPay-Signature header
Poll get_payment_status() in a tight loop Use webhooks or await_settlement() with timeouts
Assume settlement = payment received Cross-check with bank reference number in your account
Hardcode bank account or merchant ID Load from environment variables (os.getenv())
Block on await_settlement() forever Always set a reasonable timeout (1 hour is typical)

FAQ

Q: What if the customer's bank is slow to process the transfer?

A: await_settlement() has a configurable timeout (default 3600 seconds). If it expires, the payment is still valid—the bank may confirm it later. Your webhook will fire when it does. Never assume timeout = failure; check the payment status or wait for the webhook.

Q: Can I use AgentPay VN for subscription billing?

A: Not in v1.0. AgentPay VN is built for one-time payments. For subscriptions, create a new payment request each billing cycle with a new idempotency key.

Q: Does AgentPay VN hold my money?

A: No. Money flows directly from the customer's bank to your merchant bank account. AgentPay VN is a facilitator, not a payment processor. You control the funds immediately upon settlement.

Q: What happens if my webhook endpoint is down when settlement occurs?

A: AgentPay VN will retry the webhook up to 5 times over 24 hours (exponential backoff). In the meantime, you can query get_payment_status(payment_id) to catch up. Always implement reconciliation logic.

Key Takeaways

Get Started Now

Agent-native payments are no longer experimental. With AgentPay VN, your AI agents can collect money reliably, transparently, and with settlement confirmations built in.

Install AgentPay VN:

pip install agentpay-vn

Read the full docs and API reference: https://agentpay.servicesai.vn/v1/docs

Explore the source code (MIT licensed): https://github.com/phuocdu/agentpay-vn

Start with a test payment today. Build your first agent-native payment flow. Then scale with confidence knowing your idempotency and settlement are rock-solid.

Happy coding. 🚀

Get started →

← All posts