By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
  • Home
  • Products
  • Agents
  • Capital
  • Commerce
Reading: AP2 Mandate Chain Python Tutorial: Intent to Cart to Pay
Sign In
  • Join US
Font ResizerAa
  • Home
  • Products
  • Agents
Search
  • Home
  • Products
  • Agents
  • Capital
  • Commerce
Have an existing account? Sign In
Follow US
> Blog > Commerce > AP2 Mandate Chain Python Tutorial: Intent to Cart to Pay
Diagram of the AP2 mandate chain showing a signed Intent Mandate flowing into a merchant Cart Mandate and a user-signed Payment Mandate settling in USDC
Commerce

AP2 Mandate Chain Python Tutorial: Intent to Cart to Pay

Surya Koritala
Last updated: June 3, 2026 12:04 am
By Surya Koritala
40 Min Read
Share
SHARE

Hand-roll the full signed three-mandate chain in Python: sign an Intent Mandate, expose a price-locking Cart Mandate endpoint, bind a Payment Mandate, verify every signature, then settle in USDC over a2a-x402.

Contents
  • What is the AP2 mandate chain and why hand-roll it in Python?
  • How do the three AP2 mandate types differ (Intent, Cart, Payment)?
  • Step 1 — Model the mandates and sign with Ed25519
  • Step 2 — Build and sign the Intent Mandate (‘buy shoes under $120’)
  • Step 3 — Expose a merchant Cart Mandate endpoint that price-locks line items
  • Step 4 — Generate the Payment Mandate referencing the cart hash
  • How do you validate the full AP2 signature chain end to end?
        • Pros
        • Cons
  • How do you wire a2a-x402 so the mandate authorizes a USDC settlement?
  • Running the full Intent to Cart to Pay chain locally
  • Builder’s take
  • Frequently asked questions
    • What is the AP2 mandate chain?
    • How do I build and sign an AP2 Cart Mandate in Python?
    • What is user_authorization in an AP2 Payment Mandate?
    • How does a2a-x402 let an AP2 mandate settle in USDC?
    • Why hand-roll the AP2 chain instead of cloning Google’s quickstart?
    • What signing algorithm does AP2 use for mandates?
  • Primary sources

What is the AP2 mandate chain and why hand-roll it in Python?

The AP2 mandate chain is three cryptographically signed contracts — an Intent Mandate, a Cart Mandate, and a Payment Mandate — that together prove a specific human authorized a specific purchase at a specific price, and this AP2 mandate chain Python tutorial builds and signs all three by hand instead of cloning Google’s demo. AP2 (the Agent Payments Protocol) was announced by Google on September 16, 2025 with 60+ partners including Mastercard, PayPal, Coinbase, American Express, and Salesforce, and its trust model rests entirely on those three signed mandate types.

The reason to hand-roll it is simple: every ranking tutorial right now stops at uv sync and “run the shopping demo.” That teaches you the directory layout, not the trust model. When something breaks in production — a cart that re-serializes differently, a payment that references a stale cart hash, a signature that verifies in the sample but not against your own verifier — you need to understand the bytes that get signed. The AP2 reference implementation (v0.2.0, shipped April 2026) is a Python-majority codebase with TypeScript, Kotlin, and Go references, and its mandates are Pydantic models. We will mirror those models closely enough that your hand-rolled chain interoperates conceptually, while staying small enough to read in one sitting.

Here is the mental model. The user’s agent signs an Intent Mandate (“buy running shoes under $120”). The merchant agent receives that intent, picks line items, computes a total, and signs a Cart Mandate — the merchant signature is a price-lock: it commits the merchant to those exact items at that exact price. The user’s agent (or a credentials provider acting for the human) then signs a Payment Mandate that references the Cart Mandate’s hash, producing a verifiable presentation the network can audit. Each link points at the previous one by hash, so you cannot tamper with the cart after the human approved it without breaking the chain.

By the end you will have: mandate dataclasses, canonical JSON hashing, Ed25519 sign/verify helpers, a FastAPI Cart Mandate endpoint that price-locks line items, end-to-end signature validation, and an a2a-x402 hook that lets the same approved mandate authorize a USDC settlement with a card-equivalent audit trail.

Diagram of the AP2 mandate chain showing a signed Intent Mandate flowing into a merchant Cart Mandate and a user-signed Payment Mandate settling in USDC
Image.

Python 3.11+, and two libraries: pip install cryptography fastapi uvicorn pydantic. The cryptography package gives us Ed25519; FastAPI gives us the merchant endpoint. No blockchain node is required for the core chain — the x402 settlement section uses signed off-chain authorizations.

How do the three AP2 mandate types differ (Intent, Cart, Payment)?

An Intent Mandate captures the user’s constraints before any cart exists; a Cart Mandate is a merchant-signed object that price-locks exact line items and a total; and a Payment Mandate carries the user’s authorization signed over both the cart and payment hashes. They are not interchangeable — each one is signed by a different party and commits to a different fact, and the chain only validates if every link references the one before it.

The Intent Mandate is signed by the buyer side and answers “what is the human willing to authorize?” — a natural-language or structured goal plus hard constraints like a price ceiling, allowed merchants, or an expiry. In AP2 this supports both human-present and, as of v0.2, “Human Not Present” autonomous flows. The Cart Mandate is signed by the merchant and answers “what exactly is being sold and for how much?” — its contents (items, unit prices, total, currency) plus a merchant signature act as the price-lock. The Payment Mandate is signed by the user (often via a credentials provider) and answers “which instrument pays, and does the human approve this specific cart?” — its user_authorization field is a base64url verifiable presentation signing over the cart and payment hashes.

The table below is the cheat sheet I keep open while building. Note the signer column: getting the wrong party to sign a mandate is the single most common conceptual bug, and it is the one the clone-the-demo tutorials never explain because the sample does it for you.

A merchant signature over a cart that omits shipping, tax, or currency is not a price-lock — it is theater. Whatever the merchant signs is what the human is agreeing to. Hash the complete canonical cart, including totals and currency, before signing.

MandateSigned byCommits toReferencesKey fields
Intent MandateBuyer / user agentConstraints & goal (“shoes under $120”)Nothing (chain root)natural_language_description, merchants, max_amount, expiry, user_signature
Cart MandateMerchant agentExact line items + total (price-lock)Intent Mandate (optional)contents (items, total, currency), merchant_authorization, timestamp
Payment MandateUser / credentials providerChosen instrument + cart approvalCart Mandate hashpayment_details, payment_method, user_authorization (VP over hashes)
x402 settlementClient agent (on-chain auth)USDC transfer to merchantPayment MandateEIP-3009 authorization, signature, payTo, asset
The three AP2 mandate types: who signs each, what it commits to, and what it references.

Step 1 — Model the mandates and sign with Ed25519

Start by modeling each mandate as a dataclass, defining canonical JSON so the same object always produces the same bytes, then sign that canonical form with Ed25519. AP2’s reference models are Pydantic, but plain dataclasses keep this tutorial dependency-light and make the signed bytes obvious. The critical discipline is canonicalization: Ed25519 verifies bytes, not your intent, so if your verifier re-serializes the object even slightly differently, verification fails.

Two helpers do all the cryptographic work. canonical_bytes serializes a payload with sorted keys and no whitespace; mandate_hash gives you a stable SHA-256 hex digest that downstream mandates reference. We sign the canonical bytes of the mandate’s contents (not the wrapper that carries the signature), so the signature can be attached without invalidating itself.

A mandate object holds both the data and the signature over that data. If you sign the whole object, you create a chicken-and-egg problem: the signature is part of what you are signing. Separate contents (signed) from the envelope (carries signature + kid). This mirrors how AP2 separates CartContents from the signed CartMandate.

# ap2_chain/crypto.py
import json, hashlib, base64
from dataclasses import dataclass, field
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PrivateKey, Ed25519PublicKey,
)
from cryptography.hazmat.primitives import serialization
from cryptography.exceptions import InvalidSignature


def canonical_bytes(payload: dict) -> bytes:
    """Deterministic JSON: sorted keys, no whitespace. Sign THESE bytes."""
    return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()


def mandate_hash(payload: dict) -> str:
    """Stable digest a downstream mandate commits to."""
    return hashlib.sha256(canonical_bytes(payload)).hexdigest()


def b64url(raw: bytes) -> str:
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()


def b64url_decode(s: str) -> bytes:
    pad = "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s + pad)


@dataclass
class Signer:
    """A party (user, merchant, credentials provider) with an Ed25519 key."""
    kid: str                      # key id, e.g. 'did:user:alice#k1'
    _sk: Ed25519PrivateKey = field(default_factory=Ed25519PrivateKey.generate)

    def public_b64(self) -> str:
        raw = self._sk.public_key().public_bytes(
            serialization.Encoding.Raw, serialization.PublicFormat.Raw,
        )
        return b64url(raw)

    def sign(self, payload: dict) -> str:
        return b64url(self._sk.sign(canonical_bytes(payload)))


def verify(public_b64_key: str, payload: dict, signature_b64: str) -> bool:
    pk = Ed25519PublicKey.from_public_bytes(b64url_decode(public_b64_key))
    try:
        pk.verify(b64url_decode(signature_b64), canonical_bytes(payload))
        return True
    except InvalidSignature:
        return False

Step 2 — Build and sign the Intent Mandate (‘buy shoes under $120’)

The Intent Mandate is the root of the chain: the buyer agent encodes the human’s goal and hard constraints, then the user’s key signs the canonical contents. This is where the price ceiling lives. Everything downstream — the cart the merchant builds, the payment that settles — must respect what the human committed to here, and your validator will enforce it.

Model the contents with the fields that actually constrain a purchase: a description, a maximum amount with currency, an allowlist of merchants, and an expiry so a leaked intent cannot be replayed forever. Then wrap it in an envelope carrying the signer’s key id and signature.

With the Intent Mandate signed, the buyer agent ships it to the merchant agent. The merchant never gets to change the ceiling — if it returns a cart over $120 or from a merchant not on the allowlist, your validator (covered below) rejects the whole chain. This is the first place a hand-rolled build beats the demo: you see exactly which field the human committed to and exactly where it is enforced.

# ap2_chain/mandates.py
import time
from dataclasses import dataclass, asdict
from .crypto import Signer, verify, mandate_hash


@dataclass
class IntentContents:
    natural_language_description: str   # "buy running shoes under $120"
    max_amount: str                     # "120.00"
    currency: str                       # "USD"
    allowed_merchants: list[str]
    expiry_unix: int
    human_present: bool = True


@dataclass
class IntentMandate:
    contents: dict          # asdict(IntentContents)
    kid: str                # user's key id
    signature: str          # base64url Ed25519 over canonical(contents)

    @staticmethod
    def issue(user: Signer, contents: IntentContents) -> "IntentMandate":
        body = asdict(contents)
        return IntentMandate(contents=body, kid=user.kid,
                             signature=user.sign(body))

    def verify_with(self, user_pubkey: str) -> bool:
        return verify(user_pubkey, self.contents, self.signature)

    def hash(self) -> str:
        return mandate_hash(self.contents)


# --- usage ---
user = Signer(kid="did:user:alice#k1")
intent = IntentMandate.issue(user, IntentContents(
    natural_language_description="buy running shoes under $120",
    max_amount="120.00", currency="USD",
    allowed_merchants=["merchant.example"],
    expiry_unix=int(time.time()) + 3600,
))
assert intent.verify_with(user.public_b64())
print("intent hash:", intent.hash())

Step 3 — Expose a merchant Cart Mandate endpoint that price-locks line items

The Cart Mandate endpoint receives the Intent Mandate, builds a concrete cart of line items, computes the total, and returns a CartMandate whose merchant signature locks those exact items and that exact price. This is the heart of AP2 and the part every clone-the-demo post skips. The merchant signature is the price-lock: by signing the canonical cart contents, the merchant becomes contractually bound to honor that price when settlement happens.

Build it with FastAPI. The endpoint verifies the inbound Intent Mandate, refuses to assemble a cart that violates the intent’s ceiling, computes a deterministic total, signs the canonical CartContents with the merchant key, and returns the signed mandate plus the merchant’s public key so the buyer can verify offline. Crucially, the response includes the cart_hash — the SHA-256 of the canonical cart — because the Payment Mandate references it.

Run it with uvicorn ap2_chain.merchant:app --port 8001. Notice the ordering inside the handler: verify the intent signature, then enforce the constraints, then quote. Quoting before enforcing is how you accidentally build a cart the human never authorized. The returned cart_hash is the join key for the next mandate.

Use Python’s Decimal for money. A float total like 109.00000000001 changes the canonical bytes and therefore the cart hash, silently breaking the chain. Quote prices as strings, parse with Decimal, and serialize back to strings.

# ap2_chain/merchant.py
import time
from decimal import Decimal
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from .crypto import Signer, verify, mandate_hash

MERCHANT = Signer(kid="did:merchant:example#k1")
app = FastAPI()


class IntentIn(BaseModel):
    contents: dict
    kid: str
    signature: str
    user_pubkey: str   # buyer ships its public key for verification


CATALOG = {
    "sku-runner-7": {"name": "Trailblaze Runner", "price": "109.00"},
}


def build_cart(intent: dict) -> dict:
    item = CATALOG["sku-runner-7"]
    line_total = Decimal(item["price"])
    shipping = Decimal("0.00")
    total = line_total + shipping
    return {
        "merchant_id": "merchant.example",
        "currency": intent["currency"],
        "items": [{"sku": "sku-runner-7", "name": item["name"],
                    "qty": 1, "unit_price": item["price"]}],
        "shipping": str(shipping),
        "total": str(total),
        "expiry_unix": int(time.time()) + 600,
    }


@app.post("/cart-mandate")
def cart_mandate(intent: IntentIn):
    # 1. Verify the buyer actually signed this intent.
    if not verify(intent.user_pubkey, intent.contents, intent.signature):
        raise HTTPException(400, "intent signature invalid")
    # 2. Enforce the human's constraints BEFORE quoting.
    if "merchant.example" not in intent.contents["allowed_merchants"]:
        raise HTTPException(403, "merchant not allowed by intent")
    cart = build_cart(intent.contents)
    if Decimal(cart["total"]) > Decimal(intent.contents["max_amount"]):
        raise HTTPException(409, "no cart fits the intent ceiling")
    # 3. Sign the canonical cart -> this is the price-lock.
    merchant_sig = MERCHANT.sign(cart)
    return {
        "contents": cart,
        "merchant_authorization": merchant_sig,
        "merchant_kid": MERCHANT.kid,
        "merchant_pubkey": MERCHANT.public_b64(),
        "cart_hash": mandate_hash(cart),
    }

Step 4 — Generate the Payment Mandate referencing the cart hash

The Payment Mandate binds a payment instrument to the approved cart by signing a user_authorization — a verifiable presentation over the cart hash and the payment hash — so the human’s approval is cryptographically welded to that exact cart. This is what makes AP2 tamper-evident: because the Payment Mandate commits to the Cart Mandate’s hash, a merchant cannot swap the cart after the human approved it without invalidating the user’s signature.

On the buyer side, the user agent receives the signed Cart Mandate, re-verifies the merchant signature and re-derives the cart hash itself (never trust the merchant’s claimed hash), then constructs the Payment Mandate. The user_authorization is built as a verifiable presentation: a base64url-encoded object that signs over both the cart hash and the payment-details hash. AP2’s reference models store exactly this kind of presentation in the Payment Mandate’s user_authorization field.

The two assertions in steps 1 and 2 of the code are the entire security argument for the chain. The buyer never accepts the merchant’s word for the price or the hash — it recomputes both. The vp object is the verifiable presentation; we carry both its base64url form (for transport and audit) and the raw Ed25519 signature over it (for verification). Now every link points backward by hash: Payment to Cart, and the cart was built from the Intent.

# ap2_chain/payment.py
import time, json
from decimal import Decimal
from .crypto import Signer, verify, mandate_hash, b64url


def build_payment_mandate(user: Signer, cart_resp: dict,
                          intent_contents: dict, method: str) -> dict:
    cart = cart_resp["contents"]
    # 1. Re-verify the merchant price-lock locally.
    assert verify(cart_resp["merchant_pubkey"], cart,
                  cart_resp["merchant_authorization"]), "bad merchant sig"
    # 2. Re-derive the cart hash; do NOT trust the merchant's number.
    cart_hash = mandate_hash(cart)
    assert cart_hash == cart_resp["cart_hash"], "cart hash mismatch"
    # 3. Re-check it still fits the human's intent.
    assert Decimal(cart["total"]) <= Decimal(intent_contents["max_amount"])

    payment_details = {
        "amount": cart["total"],
        "currency": cart["currency"],
        "payment_method": method,           # "CARD" or "x402-usdc"
        "merchant_id": cart["merchant_id"],
        "timestamp": int(time.time()),
    }
    payment_hash = mandate_hash(payment_details)

    # 4. user_authorization = verifiable presentation over BOTH hashes.
    vp = {"cart_mandate_hash": cart_hash,
          "payment_mandate_hash": payment_hash,
          "holder": user.kid}
    user_authorization = b64url(json.dumps(vp, sort_keys=True,
                                separators=(",", ":")).encode())
    return {
        "payment_details": payment_details,
        "cart_mandate_hash": cart_hash,
        "user_authorization": user_authorization,
        "user_authorization_sig": user.sign(vp),
        "user_kid": user.kid,
        "user_pubkey": user.public_b64(),
    }

How do you validate the full AP2 signature chain end to end?

End-to-end validation walks the chain backward — verify the user_authorization over both hashes, re-derive the cart hash and confirm the Payment Mandate references it, verify the merchant price-lock, and confirm the cart still satisfies the signed Intent Mandate — rejecting the transaction if any single check fails. A mandate chain is only as trustworthy as its weakest verification, so the validator is the most important code you will write, not the mandate construction.

The validator below takes the three mandates plus the public keys of the user and merchant and returns a structured result. It is intentionally paranoid: it re-derives every hash from canonical bytes rather than trusting any field that travels alongside the data it describes. This is the function the clone-the-demo tutorials never give you, and it is the function you will actually run in production behind your checkout.

Pros
  • You understand exactly which bytes get signed, so signature bugs are debuggable instead of magic
  • You control canonicalization and hashing, the two things that silently break interop
  • The validator is yours to extend with business rules (velocity limits, allowlists, expiry)
  • Trivial to swap Ed25519 for a KMS/HSM signer without rewriting the protocol logic
  • Maps cleanly onto AP2’s Pydantic models when you graduate to the official SDK
Cons
  • You are responsible for canonical JSON correctness, not the library
  • No built-in A2A transport, agent cards, or discovery — you wire HTTP yourself
  • Key management and rotation are on you from day one
  • You should still validate against the official v0.2.0 reference before going to production
# ap2_chain/validate.py
import time, json
from decimal import Decimal
from .crypto import verify, mandate_hash, b64url_decode


def validate_chain(intent: dict, cart_resp: dict, payment: dict,
                   user_pubkey: str) -> dict:
    errors = []

    # 1. Intent Mandate signed by the user.
    if not verify(user_pubkey, intent["contents"], intent["signature"]):
        errors.append("intent signature invalid")

    # 2. Cart Mandate: merchant price-lock holds.
    cart = cart_resp["contents"]
    if not verify(cart_resp["merchant_pubkey"], cart,
                  cart_resp["merchant_authorization"]):
        errors.append("merchant signature invalid")
    cart_hash = mandate_hash(cart)

    # 3. Payment Mandate references THIS cart hash.
    if payment["cart_mandate_hash"] != cart_hash:
        errors.append("payment does not reference the signed cart")

    # 4. user_authorization is a VP over both hashes, user-signed.
    vp = json.loads(b64url_decode(payment["user_authorization"]))
    pay_hash = mandate_hash(payment["payment_details"])
    if vp.get("cart_mandate_hash") != cart_hash:
        errors.append("VP cart hash mismatch")
    if vp.get("payment_mandate_hash") != pay_hash:
        errors.append("VP payment hash mismatch")
    if not verify(user_pubkey, vp, payment["user_authorization_sig"]):
        errors.append("user_authorization signature invalid")

    # 5. Business rules: cart honors the signed intent.
    ic = intent["contents"]
    if Decimal(cart["total"]) > Decimal(ic["max_amount"]):
        errors.append("cart exceeds intent ceiling")
    if cart["merchant_id"] not in ic["allowed_merchants"]:
        errors.append("merchant not allowed by intent")
    if int(time.time()) > ic["expiry_unix"]:
        errors.append("intent expired")

    return {"valid": not errors, "errors": errors,
            "cart_hash": cart_hash, "payment_hash": pay_hash}


# Reject on ANY error. A mandate chain is all-or-nothing.
# result = validate_chain(asdict(intent), cart_resp, payment, user.public_b64())
# assert result["valid"], result["errors"]

How do you wire a2a-x402 so the mandate authorizes a USDC settlement?

The a2a-x402 extension lets a validated AP2 Payment Mandate authorize an x402 USDC settlement: the merchant returns an HTTP 402 challenge with payment requirements, the client signs an EIP-3009 transferWithAuthorization referencing the mandate, and a facilitator submits it on-chain — giving stablecoin settlement a card-equivalent audit trail. This is the ap2 x402 stablecoin settlement path, and it reuses the exact mandate chain you just built rather than inventing a new approval.

x402 turns the dormant HTTP 402 “Payment Required” status into a working machine-to-machine settlement layer. The flow is one extra round trip: the client requests the resource, the merchant responds 402 with PaymentRequirements (asset, amount, payTo address, network), the client signs an off-chain EIP-3009 authorization for USDC, retries with an X-PAYMENT header, and a facilitator calls transferWithAuthorization on the USDC contract to settle. Because USDC natively implements EIP-3009, the buyer signs an off-chain authorization and the facilitator broadcasts it — no on-chain approval and no gas held by the buyer. The a2a-x402 extension, developed with Coinbase, the Ethereum Foundation, and MetaMask, maps the AP2 Payment Mandate onto this flow so the same human approval that authorized the cart also authorizes the transfer.

The hook below takes a validated mandate result and produces the x402 payment payload, binding the AP2 cart_mandate_hash into the EIP-3009 nonce so the on-chain settlement is provably tied to the off-chain mandate. The facilitator cannot change the amount or the destination — it is only a broadcaster — so your card-equivalent audit trail is intact: cart hash, payment hash, and on-chain transfer all reference one another.

The signature line is commented because it requires a funded wallet and an EVM library (such as eth-account for EIP-712 or eth_defi.usdc.eip_3009); the structure, however, is complete and matches the x402 exact-EVM scheme. The important design point is the ap2 block and the cart-hash-derived nonce: they make the on-chain USDC transfer point back at the off-chain AP2 mandate, so an auditor can trace a stablecoin settlement to the human’s signed approval the same way a card chargeback traces to an authorization.

# ap2_chain/x402_hook.py
import time
from decimal import Decimal

USDC_DECIMALS = 6  # USDC uses 6 decimals


def payment_requirements(cart: dict, pay_to: str, network="base") -> dict:
    """What a merchant returns inside an HTTP 402 response."""
    atomic = int(Decimal(cart["total"]) * (10 ** USDC_DECIMALS))
    return {
        "scheme": "exact",
        "network": network,
        "asset": "USDC",
        "maxAmountRequired": str(atomic),
        "payTo": pay_to,
        "maxTimeoutSeconds": 300,
    }


def build_x402_authorization(validated: dict, cart: dict,
                             from_addr: str, pay_to: str) -> dict:
    """Bind the AP2 mandate to an EIP-3009 transferWithAuthorization.
    The cart_mandate_hash becomes part of the nonce, welding the
    off-chain mandate to the on-chain settlement."""
    assert validated["valid"], "refuse to settle an invalid mandate chain"
    atomic = int(Decimal(cart["total"]) * (10 ** USDC_DECIMALS))
    now = int(time.time())
    # 32-byte nonce derived from the cart hash -> audit linkage.
    nonce = "0x" + validated["cart_hash"][:64]
    authorization = {
        "from": from_addr,
        "to": pay_to,
        "value": str(atomic),
        "validAfter": str(now - 5),
        "validBefore": str(now + 300),
        "nonce": nonce,
    }
    # In production you sign `authorization` with the wallet key via
    # EIP-712 (transferWithAuthorization) and put it in X-PAYMENT.
    return {
        "x402Version": 1,
        "scheme": "exact",
        "network": "base",
        "payload": {
            "authorization": authorization,
            "ap2": {
                "cart_mandate_hash": validated["cart_hash"],
                "payment_mandate_hash": validated["payment_hash"],
            },
            # "signature": eip712_sign(authorization, wallet_key)
        },
    }


# The facilitator then calls USDC.transferWithAuthorization(...) with
# payload.authorization + payload.signature. It can neither change the
# amount nor the destination -> card-equivalent, mandate-bound audit trail.

“The merchant signature on the Cart Mandate is the price-lock. Everything else in AP2 exists to prove a human approved that exact locked cart.”

Surya Koritala, founder of Cyntr and Loomfeed

Running the full Intent to Cart to Pay chain locally

To run the whole chain, start the merchant endpoint, sign an Intent Mandate, POST it to get a price-locked Cart Mandate, build the Payment Mandate against the cart hash, validate end to end, then emit the x402 authorization. This is the complete loop the AP2 sample hides behind a shell script — here every step is yours and inspectable.

The driver below ties the previous steps together. Run the merchant with uvicorn ap2_chain.merchant:app --port 8001 in one terminal, then execute this driver in another. You will see the cart hash printed by the merchant match the hash your validator re-derives, and the final x402 payload carry both mandate hashes.

A complete, signed AP2 mandate chain: an Intent Mandate the human signs, a merchant Cart Mandate that price-locks line items, a Payment Mandate whose verifiable presentation commits to the cart hash, an end-to-end validator, and an a2a-x402 hook that authorizes a USDC settlement bound to the same mandate. That is the full Intent to Cart to Pay trust model — not a demo clone.

# run_chain.py
import time, requests
from dataclasses import asdict
from ap2_chain.crypto import Signer
from ap2_chain.mandates import IntentMandate, IntentContents
from ap2_chain.payment import build_payment_mandate
from ap2_chain.validate import validate_chain
from ap2_chain.x402_hook import build_x402_authorization

user = Signer(kid="did:user:alice#k1")

# 1. Intent: buy shoes under $120
intent = IntentMandate.issue(user, IntentContents(
    natural_language_description="buy running shoes under $120",
    max_amount="120.00", currency="USD",
    allowed_merchants=["merchant.example"],
    expiry_unix=int(time.time()) + 3600,
))

# 2. Ask the merchant for a price-locked Cart Mandate.
cart_resp = requests.post("http://localhost:8001/cart-mandate", json={
    "contents": intent.contents, "kid": intent.kid,
    "signature": intent.signature, "user_pubkey": user.public_b64(),
}).json()
print("merchant cart_hash:", cart_resp["cart_hash"])

# 3. Build the Payment Mandate referencing the cart hash.
payment = build_payment_mandate(user, cart_resp, intent.contents,
                                method="x402-usdc")

# 4. Validate the whole chain before settling.
result = validate_chain(asdict(intent), cart_resp, payment,
                        user.public_b64())
assert result["valid"], result["errors"]
print("chain valid:", result["valid"], "cart_hash:", result["cart_hash"])

# 5. Authorize a USDC settlement via a2a-x402.
x402 = build_x402_authorization(
    result, cart_resp["contents"],
    from_addr="0xBuyerWallet", pay_to="0xMerchantWallet")
print("x402 binds AP2 mandate:", x402["payload"]["ap2"])
Troubleshooting: ‘user_authorization signature invalid’ even though I just signed itThis is almost always a canonicalization mismatch. You signed the vp dict but verified against a re-serialized version with different key order or whitespace. Make sure both sign and verify go through the same canonical_bytes with sort_keys=True, separators=(',',':'). Never sign one serialization and verify another.
Troubleshooting: ‘cart hash mismatch’ between merchant and buyerThe merchant and buyer serialized the cart differently. The usual culprit is a float total (use Decimal and emit strings), a key the merchant added after hashing, or a non-ASCII character serialized with different escaping. Hash the EXACT object the merchant signed, and have the merchant return that object verbatim — do not rebuild it on the buyer side.
Troubleshooting: ‘payment does not reference the signed cart’Your Payment Mandate’s cart_mandate_hash was computed over a modified cart. Re-derive the hash from cart_resp['contents'] directly and confirm it equals cart_resp['cart_hash'] before building the payment. If the merchant changed even shipping after signing, the chain correctly refuses to validate.
Troubleshooting: x402 facilitator rejects the authorizationCheck that validAfter/validBefore bracket the current time, the value is in atomic units (USDC has 6 decimals, so $109.00 is 109000000), and the nonce is a 32-byte hex string. The facilitator can only broadcast; it cannot fix amounts or destinations, so any mismatch is on the payload.

Builder’s take

I build agentic commerce plumbing for a living at Cyntr and Loomfeed, and the thing nobody tells you about AP2 is that the demo repo hides the one part that actually matters: the signatures. Cloning the CineAgent sample teaches you the happy path, not the trust model. Here is what I tell my own team when we wire mandates.

  • The Cart Mandate is the whole ballgame. If the merchant signature does not cover the exact line items and total, you have a price-lock that does not lock anything. Hash the canonical cart, sign that, and reject any cart whose recomputed hash drifts by a byte.
  • Treat user_authorization as a verifiable presentation over two hashes, not a vibe. The Payment Mandate must commit to the Cart Mandate hash so a compromised merchant cannot swap the cart after the human approved it.
  • Canonical JSON is not optional. Ed25519 verifies bytes, not intent. Sort keys, drop whitespace, and pin the encoding before you sign, or your verifier will fail on a re-serialization you did not notice.
  • The a2a-x402 hook is the cleanest part of the stack. Once the mandate chain validates, an x402 402-challenge plus an EIP-3009 transferWithAuthorization gives you USDC settlement with a card-equivalent audit trail and no merchant account.
  • Keep the keys boring. Ed25519 from the standard cryptography library is enough to learn the protocol. Swap to an HSM or a KMS-backed signer for production, but do not let key management block you from understanding the chain first.

Frequently asked questions

What is the AP2 mandate chain?

The AP2 mandate chain is three cryptographically signed contracts in the Agent Payments Protocol: an Intent Mandate (the human’s constraints, like ‘buy shoes under $120’), a Cart Mandate (a merchant-signed object that price-locks exact line items and a total), and a Payment Mandate (the user’s authorization, signed as a verifiable presentation over the cart and payment hashes). Each link references the previous one by hash, so the chain is tamper-evident: you cannot change the cart after the human approved it without breaking a signature.

How do I build and sign an AP2 Cart Mandate in Python?

Model the cart contents (items, unit prices, total, currency) as a deterministic object, serialize it to canonical JSON with sorted keys and no whitespace, then sign those bytes with the merchant’s Ed25519 key using Python’s cryptography library. Return the signed contents plus the merchant signature and the SHA-256 cart hash. That merchant signature is the price-lock: it binds the merchant to those exact items at that exact price. The full FastAPI endpoint is in Step 3 of this tutorial.

What is user_authorization in an AP2 Payment Mandate?

user_authorization is a base64url-encoded verifiable presentation that the user (or a credentials provider acting for them) signs over both the Cart Mandate hash and the Payment Mandate hash. By committing to the cart hash, it cryptographically welds the human’s approval to one specific cart, so a merchant cannot swap line items or change the price after approval. In AP2’s reference Pydantic models, this lives in the Payment Mandate’s user_authorization field.

How does a2a-x402 let an AP2 mandate settle in USDC?

Once the AP2 Payment Mandate validates, the a2a-x402 extension routes it into an x402 flow: the merchant returns HTTP 402 with payment requirements, the client signs an off-chain EIP-3009 transferWithAuthorization for USDC, and a facilitator broadcasts it on-chain. The settlement payload carries the cart and payment mandate hashes, so the USDC transfer is provably bound to the human’s signed approval, giving stablecoin payments a card-equivalent audit trail. USDC natively implements EIP-3009, so the buyer needs no on-chain approval or gas.

Why hand-roll the AP2 chain instead of cloning Google’s quickstart?

The quickstart (uv sync, run the shopping demo) teaches you the repo layout but hides the part that matters in production: which exact bytes get signed, how canonical JSON is produced, and how each hash references the previous mandate. Hand-rolling makes signature bugs debuggable instead of magic, gives you control over canonicalization and hashing (the two things that silently break interop), and lets you extend the validator with your own business rules. You can graduate to the official v0.2.0 SDK afterward with full understanding.

What signing algorithm does AP2 use for mandates?

AP2 is signature-scheme-agnostic and structures its mandates as Pydantic models with a user_authorization field that holds a verifiable presentation. This tutorial uses Ed25519 from Python’s cryptography library because it is fast, deterministic, and standard-library-grade, and it cleanly demonstrates the sign-over-canonical-bytes discipline the protocol requires. In production you swap the in-process Ed25519 key for an HSM- or KMS-backed signer without changing any of the mandate or validation logic.

Primary sources

  • AP2 reference implementation (v0.2.0) — Google Agentic Commerce / GitHub
  • AP2 Agent Payments Protocol documentation — ap2-protocol.org
  • a2a-x402 extension repository — Google Agentic Commerce / GitHub
  • AP2 (Agent Payments Protocol) usage tutorial and sample — a2aprotocol.ai
  • x402 exact EVM scheme (EIP-3009 transferWithAuthorization) — Coinbase / GitHub
  • EIP-3009 transferWithAuthorization support for Python — web3-ethereum-defi

Last updated: June 3, 2026. Related: Commerce.

Outcome-Based Pricing for AI Agents: What CX Pays in 2026
AI Agent Code Execution Sandbox: Python Tutorial (2026)
Agentic Payment Protocols Compared: ACP, AP2, x402, MPP
How to Build a Voice AI Agent in Python (2026)
RAG tutorial Python — build a production RAG pipeline in 2026
TAGGED:a2a-x402Agent Payments ProtocolAgentic CommerceAP2Ed25519FastAPIPythonUSDCVerifiable Mandatesx402
Share This Article
Facebook Email Copy Link Print
Leave a Comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

More Popular from Alatirok

Reference architecture diagram showing an AI agent calling a website's NLWeb /ask endpoint, which extracts Schema.org JSON-LD into a vector store and exposes an MCP server
Agent Infrastructure

What Is NLWeb? Microsoft’s Agentic Web Protocol Explained

By Surya Koritala
28 Min Read
What Is Cognition Devin? The Enterprise Guide for

What Is Cognition Devin? The Enterprise Guide for 2026

By Surya Koritala
An AI agent connected to a virtual credit card with a spending limit gauge, illustrating agentic commerce controls in 2026
Commerce

How to Give an AI Agent a Credit Card With a Spending Limit

By Surya Koritala
31 Min Read
Agent Infrastructure

Azure Agent Mesh Tutorial: Deploy a Federated Agent

This azure agent mesh tutorial is the first hands-on deploy: target the Mesh with Agent Framework…

By Surya Koritala
Capital

LLM Long-Context Pricing Surcharge 2026: The Cliff Mapped

Long-context pricing surcharge: The LLM long context pricing surcharge 2026 doubles your whole request the moment…

By Surya Koritala

What Is Claude Cowork? Architecture, Cost, and Limits

What is Claude Cowork? A technical, vendor-neutral guide to its sandbox architecture, real per-seat plus API…

By Surya Koritala
Commerce

Best AI Agent Marketplaces 2026: Where to Sell Agents

The best AI agent marketplaces 2026 ranked by audience, listing model, and revenue share — AgentExchange,…

By Surya Koritala

Best AI Coding CLI 2026: Claude Code vs Codex vs Antigravity

The best AI coding CLI 2026 comes down to Claude Code, Codex CLI, and Antigravity CLI.…

By Surya Koritala

what’s actually being built in AI agents, who’s building it, and why it matters. Independent. Opinionated.

Categories

  • Home
  • Products
  • Agents
  • Capital
  • Commerce

Quick Links

  • Home
  • Products
  • Agents

© Alatirok by Loomfeed. All Rights Reserved.

Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?