TatvaPay
Docs menu· Pay per call (HTTP 402)

Pay per call with HTTP 402

Next

Charge agents per API call with a 402 challenge, paid through TatvaPay and checked on your own server.

Next (sandbox). HTTP 402 works on the sandbox today, on the fake rail. Pre-funded Reserve Pay blocks and batched metering come with the payment partner.

An API that charges per call answers a call without proof with a challenge, in TatvaPay's header and in x402 version 2 form:

HTTP/1.1 402 Payment Required
TatvaPay-Payment-Required: scheme="tatvapay-v1", quote="qt_…", amount="1000",
  currency="INR", pay="https://api.tatvapay.com/v1/pay/intents", expires="…"
PAYMENT-REQUIRED: <base64 JSON: {"x402Version": 2, "accepts": [{"scheme": "tatvapay-upi",
  "network": "upi:in", "amount": "1000", "asset": "INR", "payTo": "agt_…", …}]}>

The buyer agent pays the quote under its mandate. The payment comes back with a payment proof: a short token signed by TatvaPay. The agent calls again with it:

TatvaPay-Payment: proof="<payment proof>"

or, the x402 way, PAYMENT-SIGNATURE carrying {"x402Version": 2, "accepted": {"scheme": "tatvapay-upi"}, "payload": {"proof": "…"}}.

Your server checks it alone

A proof names the seller, the path, the amount and an expiry, and is signed with TatvaPay's published key (/.well-known/tatvapay-keys.json). Your server checks it without calling TatvaPay: the signature, that it is to you, for this path, for at least your price, and not expired. One proof buys one call: keep the proofs you have used, or, when your API runs on several servers, call POST /v1/pay/402/redeem once per proof and TatvaPay refuses the second use.

Settlement is in rupees on UPI through the licensed partner. Crypto settlement is not offered in India.

Buyer: one line

from tatvapay.http402 import fetch_paid
response = fetch_paid(agent, httpx.Client(), "GET", "https://api.seller.example/fare-alerts/DEL-BOM",
                      max_amount_paise=5000)
import { fetchPaid } from "@tatvapay/sdk";
const response = await fetchPaid(agent, "https://api.seller.example/fare-alerts/DEL-BOM", { maxAmountPaise: 5000 });

Seller: a paywall

from tatvapay.http402 import PROOF_HEADER, Paywall, ProofRefused

paywall = Paywall(seller_agent, amount_paise=1000, description="Fare alert")

def handler(request):
    proof = request.headers.get(PROOF_HEADER)
    if proof:
        try:
            paid = paywall.redeem(proof, request.url.path)
            return {"alert": "…", "paid_with": paid.payment_id}
        except ProofRefused as exc:
            reason = str(exc)
    status, headers, body = paywall.payment_required(request.url.path)
    return Response(body, status, headers)

redeem checks the proof with the published keys only. Give used= a shared store, or remote_single_use=True, if your API runs in more than one process. Without the SDK: verify_payment_proof(proof, jwks, seller_agent_id=…, resource=…, amount_paise=…) (Python) or verifyPaymentProof (TypeScript).

The full scheme, field by field, is in the engine repository at docs/protocols/http402-upi-scheme.md.

Last updated 1 October 2026

Something unclear or wrong? Tell us