Skip to content

x402 (Beta)

x402 is an HTTP payment standard built into the protocol itself. A client hits your API without paying, gets an HTTP 402 with payment details, pays on-chain, and retries. No accounts, no subscriptions, no API keys for callers — just an XRPL wallet.

DropPay acts as the x402 facilitator: it returns payment details, verifies on-chain transactions, and records every call in your dashboard with full webhook support.

x402 vs checkout API payments

x402 is for agent-native, zero-setup access — the agent discovers payment requirements from the 402 response and pays without any prior arrangement. The checkout API payments flow is different: your server creates a payment request and hands the details to the caller explicitly. Use x402 when you want any XRPL-capable agent to pay your endpoint without being pre-configured for your system.

Beta feature

x402 is in beta. Contact us to get it enabled on your account. Pricing is set in your middleware code.


How it works

Client       →  GET /your-api/endpoint          (no payment)
Your server  →  POST /x402/payment-required     (get 402 details from DropPay)
Your server  →  HTTP 402 + X-Payment-Required header
Client       →  Pays XRP on XRPL
Client       →  GET /your-api/endpoint          (retries with X-Payment: <tx_hash>)
Your server  →  POST /x402/verify-payment       (verify tx with DropPay)
Your server  →  200 OK + response               (access granted)
DropPay      →  Records call, fires webhook

The payment proof (X-Payment) is the raw XRPL transaction hash. Valid for 5 minutes after the transaction confirms.


Setup

1. Enable x402 on your account

Contact us to get x402 enabled during the beta. Your XRPL wallet address must be configured in the dashboard first.

Your merchant handle is shown at the top of the dashboard - you'll use it in the middleware code.

2. Add middleware to your API

middleware.py
import requests

DROPPAY = "https://droppayxrp.com"
HANDLE  = "your-handle"
PRICE   = 0.10  # USD per request

def require_payment(f):
    from functools import wraps
    from flask import request, jsonify

    @wraps(f)
    def decorated(*args, **kwargs):
        proof = request.headers.get("X-Payment")

        if not proof:
            r = requests.post(f"{DROPPAY}/x402/payment-required",
                              json={"handle": HANDLE, "price_usd": PRICE,
                                    "resource": request.path})
            details = r.json()
            resp = jsonify(details)
            resp.headers["X-Payment-Required"] = r.text
            return resp, 402

        r = requests.post(f"{DROPPAY}/x402/verify-payment",
                          json={"handle": HANDLE, "tx_hash": proof,
                                "price_usd": PRICE, "resource": request.path})
        result = r.json()
        if not result.get("valid"):
            return jsonify({"error": result.get("reason", "Payment invalid")}), 402

        return f(*args, **kwargs)
    return decorated

# Usage:
@app.route("/api/translate")
@require_payment
def translate():
    return jsonify({"result": "..."})
middleware.js
const DROPPAY = "https://droppayxrp.com";
const HANDLE  = "your-handle";
const PRICE   = 0.10; // USD per request

export async function requirePayment(req, res, next) {
  const proof = req.headers["x-payment"];

  if (!proof) {
    const r = await fetch(`${DROPPAY}/x402/payment-required`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ handle: HANDLE, price_usd: PRICE,
                             resource: req.path })
    });
    const details = await r.json();
    res.setHeader("X-Payment-Required", JSON.stringify(details));
    return res.status(402).json(details);
  }

  const r = await fetch(`${DROPPAY}/x402/verify-payment`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ handle: HANDLE, tx_hash: proof,
                           price_usd: PRICE, resource: req.path })
  });
  const result = await r.json();
  if (!result.valid) {
    return res.status(402).json({ error: result.reason ?? "Payment invalid" });
  }

  next();
}

// Usage:
app.get("/api/translate", requirePayment, (req, res) => {
  res.json({ result: "..." });
});

3. Test it

Call your endpoint without a payment header - you should receive an HTTP 402 with payment details. Use the XRPL AI Starter Kit to test with an AI agent, or any XRPL wallet to test manually.


Endpoint reference

POST /x402/payment-required

Returns payment details for an HTTP 402 response. Call this when a request arrives without an X-Payment header.

Request

{
  "handle":    "your-handle",
  "price_usd": 0.10,
  "resource":  "/api/translate"
}
Field Type Required Description
handle string Yes Your DropPay merchant handle
price_usd number Yes Price in USD for this request
resource string No API path being protected (shown in dashboard)

Response

{
  "x402_version":    "1",
  "pay_to_address":  "rYourWalletAddress...",
  "destination_tag": 48291,
  "amount_xrp":      "0.046001",
  "amount_usd":      "0.1",
  "rate":            "2.1739",
  "network":         "xrpl-mainnet",
  "expires_at":      "2026-07-15T12:05:00Z",
  "resource":        "/api/translate",
  "facilitator":     "https://droppayxrp.com",
  "verify_url":      "https://droppayxrp.com/x402/verify-payment"
}

Return this as the HTTP 402 body and set it as the X-Payment-Required response header. The client uses pay_to_address, destination_tag, and amount_xrp to submit the XRPL payment.

Destination tag

If your wallet is exchange-hosted (e.g. on Bitrue), destination_tag will be set and is required. The payer must include it or the payment will be rejected. Self-custody wallets return null for destination_tag.


POST /x402/verify-payment

Verifies an XRPL transaction as a valid x402 payment. Call this when a request retries with an X-Payment header.

Request

{
  "handle":    "your-handle",
  "tx_hash":   "A1B2C3D4...",
  "price_usd": 0.10,
  "resource":  "/api/translate"
}
Field Type Required Description
handle string Yes Your DropPay merchant handle
tx_hash string Yes XRPL transaction hash from the X-Payment header
price_usd number Yes The same price used in the payment-required call
resource string No API path (recorded in dashboard for this payment)

Success response

{
  "valid":        true,
  "tx_hash":      "A1B2C3D4...",
  "amount_xrp":   "0.046001",
  "paid_at":      "2026-07-15T12:00:08Z",
  "explorer_url": "https://livenet.xrpl.org/transactions/A1B2C3D4...",
  "receipt_url":  "https://droppayxrp.com/receipt/dp_x_a1b2c3d4e5"
}

Failed response

{
  "valid":  false,
  "reason": "Payment proof has expired (older than 5 minutes)"
}

When valid is false, return HTTP 402 to the client. Do not grant access.


Verification rules

DropPay checks all of the following before marking a payment valid:

Check Rule
Destination Transaction must be sent to your configured wallet address
Destination tag Must match if your wallet requires one
Amount Delivered XRP must be within 5% of the current rate equivalent of price_usd
Age Transaction must have confirmed within the last 5 minutes
Replay Each tx_hash can only be used once across all DropPay merchants

Tracking

Every verified payment appears in Dashboard > Payments under all transactions. Webhooks fire on every verified payment - the same payment.confirmed event as regular payments. See Webhooks for the payload.


Who can pay

Any caller with an XRPL wallet. No DropPay account required.

  • Claude agents via droppay-mcp - install droppay-mcp and the agent handles the full payment flow automatically. Works with Claude Code, Cursor, and any MCP client.
  • Other agents - use xrpl-py (Python) or xrpl.js (JavaScript) to submit payments on-chain
  • Humans - any XRPL wallet app (Xaman, etc.)

Why DropPay vs native x402

DropPay x402 Coinbase x402
Settlement XRPL - 3-5 seconds Base - ~2 seconds
Fees Near-zero ~$0.001 per tx
Dashboard Yes - calls, revenue, top paths No
Webhooks Yes No
Caller identity Yes - payer wallet address No
Receipt URL Yes No
Emerging markets Yes - XRPL has wide reach Limited
Non-custodial Yes Yes