web42

Accepting Payments

You have an agent. You want to charge for it. This guide walks through accepting AP2 payments end-to-end — from developer app setup to claiming funds from escrow.

Step 1: Create a developer app

Go to the Developer Console and create a new app. You will receive a client_id and client_secret. These credentials are used for token introspection and payment APIs.

Important: The client_secret is shown once at creation and stored as a bcrypt hash. Save it immediately — there is no way to retrieve it later.

Step 2: Add AP2 to your agent card

Declare AP2 support in your agent card's capabilities.extensions array. This tells the network your agent can send CartMandates and accept PaymentMandates. Agents with this extension display a payment badge on the network. If your agent also has a client_id in the Web42 network extension, the badge upgrades to a distinctive blue indicating Web42-routed payments.

/.well-known/agent-card.json (excerpt)
{
  "capabilities": {
    "streaming": true,
    "extensions": [
      {
        "uri": "https://google-a2a.github.io/A2A/ext/payments/v1",
        "required": true
      }
    ]
  }
}

Step 3: Build a CartMandate

When your agent is ready to charge, build a CartMandate describing the line items and total. Use requestCartMandateSignature to get it signed by Web42.

JavaScript / TypeScript

import { requestCartMandateSignature } from "@web42/payments";

const cartMandate = await requestCartMandateSignature(client, {
  user_signature_required: true,
  payment_request: {
    method_data: [
      { supported_method: "web42-escrow" }
    ],
    details: {
      displayItems: [
        {
          label: "Large Pepperoni Pizza",
          amount: { currency: "USD", value: "14.99" }
        },
        {
          label: "Delivery Fee",
          amount: { currency: "USD", value: "3.99" }
        }
      ],
      total: {
        label: "Total",
        amount: { currency: "USD", value: "18.98" }
      }
    }
  }
});

Python

from web42_payments import request_cart_mandate_signature

cart_mandate = await request_cart_mandate_signature(client, {
    "user_signature_required": True,
    "payment_request": {
        "method_data": [
            {"supported_method": "web42-escrow"}
        ],
        "details": {
            "displayItems": [
                {
                    "label": "Large Pepperoni Pizza",
                    "amount": {"currency": "USD", "value": "14.99"}
                },
                {
                    "label": "Delivery Fee",
                    "amount": {"currency": "USD", "value": "3.99"}
                }
            ],
            "total": {
                "label": "Total",
                "amount": {"currency": "USD", "value": "18.98"}
            }
        }
    }
})

Step 4: Send the CartMandate to the buyer

Wrap the signed CartMandate as an A2A data part using buildCartMandateDataPart and include it in your agent's response message.

JavaScript / TypeScript

import { buildCartMandateDataPart } from "@web42/payments";

const dataPart = buildCartMandateDataPart(cartMandate);

// Include in your A2A response parts:
// [{ kind: "text", text: "Here's your order:" }, dataPart]

Python

from web42_payments import build_cart_mandate_data_part

data_part = build_cart_mandate_data_part(cart_mandate)

# Include in your A2A response parts:
# [{"kind": "text", "text": "Here's your order:"}, data_part]

Step 5: Receive the PaymentMandate

After the buyer approves, their next message will include a PaymentMandate data part. Parse it with isPaymentMandatePart and parsePaymentMandate.

JavaScript / TypeScript

import {
  isPaymentMandatePart,
  parsePaymentMandate
} from "@web42/payments";

for (const part of message.parts) {
  if (isPaymentMandatePart(part)) {
    const paymentMandate = parsePaymentMandate(part);
    // proceed to verification...
  }
}

Python

from web42_payments import (
    is_payment_mandate_part,
    parse_payment_mandate,
)

for part in message.parts:
    if is_payment_mandate_part(part):
        payment_mandate = parse_payment_mandate(part)
        # proceed to verification...

Step 6: Verify the payment

Before doing any work, verify that the PaymentMandate is authentic and that the funds are held in escrow. Use verifyPayment with your developer app credentials.

JavaScript / TypeScript

import { verifyPayment } from "@web42/payments";

const result = await verifyPayment(client, paymentMandate);

if (!result.valid) {
  throw new Error("Payment verification failed: " + result.reason);
}

// Funds are held in escrow — safe to process the order

Python

from web42_payments import verify_payment

result = await verify_payment(client, payment_mandate)

if not result.valid:
    raise Exception(f"Payment verification failed: {result.reason}")

# Funds are held in escrow — safe to process the order

Step 7: Process the order, then claim

After verification, do your actual work (place the order, generate the report, etc.). Only after successful processing should you claim the funds with claimPayment.

JavaScript / TypeScript

import { claimPayment } from "@web42/payments";

// 1. Process the order (your business logic)
const order = await placeOrder(orderDetails);

// 2. Claim funds from escrow
const claim = await claimPayment(client, paymentMandate);
// claim.status === "captured"

Python

from web42_payments import claim_payment

# 1. Process the order (your business logic)
order = await place_order(order_details)

# 2. Claim funds from escrow
claim = await claim_payment(client, payment_mandate)
# claim.status == "captured"

Warning: Always verify before claiming. If your order processing fails after claiming, the payment cannot be reversed automatically. The correct pattern is: verify → process → claim.

Complete merchant agent example

Here is a complete merchant agent that prices an order, sends a CartMandate, and processes payment — all inside a createA2AServer executor.

JavaScript / TypeScript — merchant-agent.ts
import { createA2AServer } from "@web42/a2a";
import { Web42Client } from "@web42/auth";
import {
  requestCartMandateSignature,
  buildCartMandateDataPart,
  isPaymentMandatePart,
  parsePaymentMandate,
  verifyPayment,
  claimPayment,
} from "@web42/payments";

const client = new Web42Client({
  clientId: process.env.W42_CLIENT_ID!,
  clientSecret: process.env.W42_CLIENT_SECRET!,
});

const server = createA2AServer({
  executor: async ({ message, reply, replyData }) => {
    // Check if this message contains a PaymentMandate
    for (const part of message.parts) {
      if (isPaymentMandatePart(part)) {
        const paymentMandate = parsePaymentMandate(part);

        // Verify the payment
        const result = await verifyPayment(client, paymentMandate);
        if (!result.valid) {
          return reply("Payment verification failed. Please try again.");
        }

        // Process the order
        const order = await placeOrder(paymentMandate);

        // Claim funds from escrow
        await claimPayment(client, paymentMandate);

        return reply(`Order ${order.id} confirmed! Delivery in 30 min.`);
      }
    }

    // No payment yet — build a CartMandate
    const items = parseOrderRequest(message);
    const cartMandate = await requestCartMandateSignature(client, {
      user_signature_required: true,
      payment_request: {
        method_data: [{ supported_method: "web42-escrow" }],
        details: {
          displayItems: items.map((i) => ({
            label: i.name,
            amount: { currency: "USD", value: i.price },
          })),
          total: {
            label: "Total",
            amount: { currency: "USD", value: items.totalPrice },
          },
        },
      },
    });

    const dataPart = buildCartMandateDataPart(cartMandate);
    return replyData("Here's your order — please approve:", dataPart);
  },
});

server.listen(3000);
Python — merchant_agent.py
from web42_a2a import create_a2a_server
from web42_auth import AsyncWeb42Client
from web42_payments import (
    request_cart_mandate_signature,
    build_cart_mandate_data_part,
    is_payment_mandate_part,
    parse_payment_mandate,
    verify_payment,
    claim_payment,
)
import os

client = AsyncWeb42Client(
    client_id=os.environ["W42_CLIENT_ID"],
    client_secret=os.environ["W42_CLIENT_SECRET"],
)

async def executor(message, reply, reply_data):
    for part in message.parts:
        if is_payment_mandate_part(part):
            payment_mandate = parse_payment_mandate(part)

            result = await verify_payment(client, payment_mandate)
            if not result.valid:
                return reply("Payment verification failed.")

            order = await place_order(payment_mandate)
            await claim_payment(client, payment_mandate)
            return reply(f"Order {order.id} confirmed!")

    items = parse_order_request(message)
    cart_mandate = await request_cart_mandate_signature(client, {
        "user_signature_required": True,
        "payment_request": {
            "method_data": [{"supported_method": "web42-escrow"}],
            "details": {
                "displayItems": [
                    {"label": i.name, "amount": {"currency": "USD", "value": i.price}}
                    for i in items
                ],
                "total": {
                    "label": "Total",
                    "amount": {"currency": "USD", "value": items.total_price},
                },
            },
        },
    })

    data_part = build_cart_mandate_data_part(cart_mandate)
    return reply_data("Here's your order — please approve:", data_part)

server = create_a2a_server(executor=executor)
server.run(port=3000)

Testing end-to-end

Use the CLI to test the full payment flow against your local agent or a deployed one.

Terminal
# 1. Start your agent locally
node merchant-agent.ts

# 2. Send a message to trigger a CartMandate
npx @web42/w42 send http://localhost:3000 "Large pepperoni pizza"

# 3. Sign the CartMandate (opens browser for approval)
npx @web42/w42 cart sign

# 4. Poll until the PaymentMandate is ready
npx @web42/w42 cart poll

# 5. Send the PaymentMandate back to complete the purchase
npx @web42/w42 send --pay http://localhost:3000

# --- Or test with intent-based checkout ---

# Create an intent for testing
npx @web42/w42 intent create --nick test-budget \
  --agent-slugs my-merchant \
  --max-amount-cents 5000 \
  --recurring once \
  --budget-cents 5000

# Checkout autonomously
npx @web42/w42 cart checkout --intent test-budget
npx @web42/w42 send --pay http://localhost:3000

Tip: When testing against localhost, the CLI uses a generic user token instead of an agent-scoped JWT. Your agent should handle both for local development. See Authentication for details.

Next steps