Agentic checkouts
The ATR hash on ACP, UCP, AP2 and ACK checkouts, on card networks, and in A2A task metadata.
Not every agent pays through a 402 challenge. Many buy through a checkout: the seller opens a checkout or a payment
request, the buyer's agent completes it, and a payment service provider, a card network or a credential provider
moves the money. This package binds H into each of these checkouts in the field its protocol provides, and says so
plainly where the protocol gives the buyer nothing to sign.
The members are the ones every pairing has. What differs is the document the seller advertises into and what the buyer presents:
| Protocol | Entry point | Pairings | Where H rides | Buyer signs H |
|---|---|---|---|---|
| ACP | @integraledger/lcp/acp | acp/checkout/delegated, acp/checkout/undelegated | The checkout session's id, and the delegate-payment allowance's checkout_session_id where the handler requires one. | no |
| UCP | @integraledger/lcp/ucp | ucp/checkout/ap2-mandate, ucp/checkout/unsigned, ucp/booking/ap2-mandate, ucp/booking/unsigned | A legal_context entry in the checkout's links[], before the business signs the checkout. | with an AP2 mandate |
| AP2 | @integraledger/lcp/ap2 | ap2/checkout-mandate | The legalContext member of the merchant-signed checkout_jwt, which the buyer's Checkout Mandate commits to. | yes |
| ACK | @integraledger/lcp/ack | ack/payment-request | The id of the seller-signed Payment Request, as H's LCP string. | no |
| Card networks | @integraledger/lcp/card | card/visa-tap, card/mastercard-vi/immediate, card/mastercard-vi/autonomous, card/seller-reference | A covered lcp-hash field of the agent's message signature, the checkout_jwt a Verifiable Intent mandate signs, or the seller's processor reference. | Visa TAP and Verifiable Intent |
| A2A | @integraledger/lcp/a2a | none | The Task's metadata, under the extension's URI. Delivery only. | no |
The "Buyer signs H" column is each pairing's pattern.buyerSigns. The
pairings reference gives the sentence each pairing's record
states about what a payment through it proves, including which signatures this package verifies and which it leaves
to the network or the provider.
Where the buyer's approval does not sign H, the seller may advertise an
agreement URL beside the link. Every checkout pairing's advertise
takes it as its last argument and writes it where the protocol keeps the link, and its read returns it as
agreement.
ACP
In the Agentic Commerce Protocol, the seller creates a checkout session and the
buyer's agent completes it with a payment handler. This package makes H the session's id, and puts the link beside
it in the session's metadata.legal_context, in snake case:
{
"id": "0x1be27e75ea3728b55834ec54ff6709669427b11c9ba47a6d9f182b0be46513be",
"metadata": {
"legal_context": {
"type": "sha256",
"value": "0x1be27e75ea3728b55834ec54ff6709669427b11c9ba47a6d9f182b0be46513be",
"legal_context_url": "https://atr.seller.example/0x1be27e75ea3728b55834ec54ff6709669427b11c9ba47a6d9f182b0be46513be"
}
}
}The ATR's binding slot, tie(options), holds one option per kind of payment handler the session offers, each ACP's
own { "requires_delegate_payment": boolean }, and nothing from the checkout. The two pairings differ in that flag:
acp/checkout/delegated: the handler requiresdelegate_payment.buildreturns the allowance the agent'sdelegate_paymentrequest carries: ACP's six members, withreason"one_time"andcheckout_session_idset to H. The agent signs its request;complete(request)returns the request only when its allowance is exactly the one built, and refusesacp/allowance-changedotherwise.boundreadscheckout_session_idback. It verifies no signature.acp/checkout/undelegated: nothing the buyer signs names the session.buildrefusesacp/nothing-to-signandboundrefusesacp/not-buyer-signed.
import { , , , } from "@integraledger/lcp";
import { , , type , type } from "@integraledger/lcp/acp";
// Seller: the session offers a handler that requires delegate_payment.
const : = { : true };
const = new ().('{"line_items":[{"id":"li_1","quantity":1}],"total":19900}');
const = await ((), ([]), [["order", ]]);
if ("refused" in ) throw new (.);
const = `https://atr.seller.example/${.}`;
const : = { : "usd", : { : "A-100" } };
const = .(, ., , );
if ("refused" in ) throw new ((.));
.("session id is H:", . === ., .(. ?? {}));
// Buyer: read H and the link, fetch and compare, then build the allowance.
const = .();
if ("refused" in ) throw new (.);
if (!(await (.), .)) throw new ("decline: hash-mismatch");
const = await .(
{ , : 19900, : "usd", : "acme", : "2026-10-01T00:00:00Z" },
.,
);
if ("refused" in ) throw new (.);
const { , ... } = .;
.("checkout_session_id is H:", === ., );
// Buyer: the agent's own stack builds and signs its delegate_payment request around the allowance.
// complete returns the request only when the allowance in it is the one built.
const = .({ : ., : { : "card" } });
if ("refused" in ) throw new ((.));
const = .({ : { ...., : 99900 } });
.("refused" in ? . : "accepted");
// Seller: H back out of what the agent signed.
const = await .();
.("bound to H:", typeof === "string" && (, .));session id is H: true [ 'order_ref', 'legal_context' ]
checkout_session_id is H: true {
reason: 'one_time',
max_amount: 19900,
currency: 'usd',
merchant_id: 'acme',
expires_at: '2026-10-01T00:00:00Z'
}
acp/allowance-changed
bound to H: trueadvertise keeps every other member and metadata key, sets the session id only when it is absent or already H
(acp/id-conflict otherwise), and refuses a session larger than 1 MiB as JSON. read takes H from id alone and
refuses a metadata.legal_context whose value is another hash (acp/legal-context-conflict). The LCP profile
acp/checkout/session-id states the rules.
UCP
In the Universal Commerce Protocol, the business returns a checkout, and, under UCP's AP2 Mandates
extension, signs it (ap2.merchant_authorization). This package appends one entry to the checkout's links[] before
the business signs it:
{ "type": "legal_context", "url": "https://atr.seller.example/0x…", "title": "lcp:sha256:0x…" }and, with an agreement URL, a second entry of type legal_context_agreement. The option names the checkout or the
booking by its own id ({ "checkout": "…" } or { "booking": "…" }), and tie(options) records those ids.
advertise refuses a checkout the business has already signed (ucp/already-signed) and one whose id is not the
option's (ucp/option-not-this-checkout).
| Pairing | Buyer side | bound |
|---|---|---|
ucp/checkout/ap2-mandate, ucp/booking/ap2-mandate | read requires the business's ap2.merchant_authorization (ucp/ap2-not-active otherwise). build returns the checkout unchanged for the buyer's mandate issuer, once its link carries H, and complete(checkout_mandate) gives what the buyer presents. | Checks AP2's checkout_hash against the checkout inside the buyer's mandate, then reads H from that checkout's legal_context link. It verifies no signature. |
ucp/checkout/unsigned, ucp/booking/unsigned | read gives H and the link. build refuses ucp/nothing-to-sign: UCP without the AP2 Mandates extension defines no buyer signature. | Refuses ucp/not-buyer-signed. |
legalContextLink(checkout) reads the one legal_context entry of any checkout: two such entries are
ucp/legal-context-conflict. The LCP profile
ucp/checkout/legal-context states the rules.
import { } from "@integraledger/lcp";
import { , , type } from "@integraledger/lcp/ucp";
const = await (new ().("the ATR's bytes"));
const = `https://atr.seller.example/${}`;
const : = {
: "chk_123",
: "ready_for_complete",
: [{ : "terms_of_service", : "https://seller.example/tos" }],
};
const = .(, , , { : "chk_123" });
if ("refused" in ) throw new ((.));
.(.);
const = ();
.("refused" in ? . : . === );
const = .(, , , { : "chk_999" });
.("refused" in ? . : "placed");
.("refused" in (await .({ : }, )));[
{ type: 'terms_of_service', url: 'https://seller.example/tos' },
{
type: 'legal_context',
url: 'https://atr.seller.example/0x3283f4fc90a2f6d2782b0345c3b274acbe0218ec84ce2d8c3a1fdbe6d1cff263',
title: 'lcp:sha256:0x3283f4fc90a2f6d2782b0345c3b274acbe0218ec84ce2d8c3a1fdbe6d1cff263'
}
]
true
ucp/option-not-this-checkout
trueAP2
In AP2, the merchant signs a checkout_jwt and the buyer's closed Checkout Mandate
commits to it through checkout_hash, the SHA-256 of that JWT. This package puts H in the JWT's payload:
- Seller.
advertise(payload, h, link, { checkout: id })appends alegalContextmember,{"type":"sha256","value":H,"legalContextUrl":link}, to the checkout payload whoseidis the option's. The seller's own stack signs the result as itscheckout_jwt. - Buyer.
read(checkoutJwt)decodes the JWT's payload and returns H, the link and the checkout.build(offer, h)returns the Checkout Mandate's required claims:vct"mandate.checkout.1", thecheckout_jwt, andcheckout_hash, the unpadded base64url of SHA-256 over the JWT. The buyer's mandate signer issues the mandate as an SD-JWT, andcomplete(checkout_mandate)gives what the buyer presents: the mandate and the JWT. - Seller, on payment.
bound(presented)finds the one closed Checkout Mandate in the SD-JWT, checks that itscheckout_hashis the hash of the presentedcheckout_jwt(ap2/checkout-not-latestotherwise), and reads H from that JWT's payload. It verifies no signature.
jwsPayload, readMandate and checkoutBinding expose those steps one by one. The LCP profile
ap2/checkout-mandate states the rules.
import { } from "@integraledger/lcp";
import { } from "@integraledger/lcp/ap2";
const = await (new ().("the ATR's bytes"));
const = { : "chk_42", : { : "USD", : "19.90" } };
const = .(, , `https://atr.seller.example/${}`, { : "chk_42" });
if ("refused" in ) throw new ((.));
.(.());
// The seller's stack signs the payload. A placeholder signature stands in: the buyer's side verifies none.
const = (: object) => .(.()).("base64url");
const = .("signature").("base64url");
const = `${({ : "ES256", : "JWT" })}.${()}.${}`;
const = .();
if ("refused" in ) throw new (.);
const = await .(., .);
if ("refused" in ) throw new (.);
.(.., ...);[ 'id', 'total', 'legalContext' ]
mandate.checkout.1 43ACK
In the payment flow of the Agent Commerce Kit (ACK), the seller answers with a
402 whose body carries a signed Payment Request token, and a receipt issued after settlement embeds that token. This package makes H the
Payment Request's id, in LCP string form, and puts the link beside the request in the 402 body:
- Seller.
advertise({}, h, link, option)returns the values the seller's stack places:paymentRequestId,lcp:sha256:0x…, for the token it signs, andlegalContext,{type, value, legalContextUrl}, for the body beside it.tie(options)records the Payment Request's options exactly as issued. - Buyer.
read(body)takes H from the signed token'sid, which must equal the body'slegalContext(ack/legal-context-conflictotherwise), and returns the token's payment options. The body's unsignedpaymentRequestcopy is never read. The token's signature is not verified here. - After settlement.
fromReceipt(credentialSubject)reads H from the Payment Request token a receipt embeds. It verifies nothing: call it on a receipt you have verified.
ACK defines no payer signature, so there is no place for H in what the buyer signs. build and bound refuse
ack/no-signed-place. The LCP profile ack/payment-request states the
rules.
Card networks
The card entry point covers three ways to pay a card checkout. The option is the seller's own
{ scheme, checkout }, where scheme is "visa-tap", "mastercard-vi" or "seller-reference" and checkout is
the seller's checkout id; pairingsOf(option) names the pairings that pay it. advertise gives the same values for
every card pairing: the legalContext the seller shows before payment, and reference, H's LCP string, for the
seller to place in its processor reference.
- Visa Trusted Agent Protocol. The agent sends
H in an
lcp-hashfield and lists that field among the covered components of itsagent-payer-authmessage signature (RFC 9421).buildreturns the field for the agent's signer to add:{ field: "lcp-hash", value: H, component: "lcp-hash" }.boundreads the onelcp-hashline when anagent-payer-authsignature lists"lcp-hash"without parameters. It verifies no signature, key, window or nonce. The LCP profilecard/visa-tapstates the rules. - Mastercard Verifiable Intent. H rides in the merchant's
checkout_jwtas itslegalContext, and a mandate signs that JWT's SHA-256 ascheckout_hash.buildreturns the checkout mandate to sign and the payment mandate'stransactionId, both that digest.card/mastercard-vi/immediatereads H from the checkout mandate the user's L2 mandate references, and checks thecheckout_hashand any disclosedtransaction_id; it verifies no signature.card/mastercard-vi/autonomousreads H from the agent's L3b checkout mandate, and verifies the ES256 signatures of L2 and L3b and theirsd_hashlinks, but not the L1 issuer's signature. The LCP profilecard/mastercard-vistates the rules. card/seller-reference. A plain card checkout: nothing the buyer signs carries H. The seller shows H and the link before payment and placesreferencein its processor reference.buildandboundrefusecard/no-signed-place.
import { } from "@integraledger/lcp";
import { , , type CardOption } from "@integraledger/lcp/card";
const = await (new ().("the ATR's bytes"));
const : CardOption = { : "visa-tap", : "order-7731" };
.((), ({ : "mastercard-vi", : "order-7731" }));
const = .({}, , `https://atr.seller.example/${}`, );
if ("refused" in ) throw new (.);
.(. === `lcp:sha256:${}`);
const = await .(, );
if ("refused" in ) throw new (.);
.(., ., . === );[ 'card/visa-tap' ] [ 'card/mastercard-vi/immediate', 'card/mastercard-vi/autonomous' ]
true
lcp-hash lcp-hash trueA2A
The Agent2Agent protocol signs nothing per transaction, so it has no place for H in a
payment. What it can do is deliver H and the link with the task. This package defines an A2A extension for that, and
exports no pairing: binding is the refusal a2a/no-signed-place, and the payment made for the task uses its own
pairing, whose record states what it proves.
| Export | What it does |
|---|---|
A2A_EXTENSION_URIS | The extension's URI: https://integraledger.com/lcp/a2a/legal-context/v1. |
agentExtension({ required? }) | The Agent Card capabilities.extensions[] entry that declares it. |
requested(header) | Whether a request's A2A-Extensions header value names the extension. |
place(task, h, link, header?) | A copy of the Task with {type, value, legalContextUrl} in its metadata, under the extension's URI. Other metadata is kept. |
read(task) | H and the link from the Task's metadata. |
delivery.proves | What the delivery shows: nothing about the payment. |
import { } from "@integraledger/lcp";
import { , , , , type A2aTask } from "@integraledger/lcp/a2a";
.(()[0]?.);
const = await (new ().("the ATR's bytes"));
const = "https://integraledger.com/lcp/a2a/legal-context/v1";
.(());
const : A2aTask = { : "task-1", : "ctx-1", : { : "input-required" }, : "task" };
const = (, , `https://atr.seller.example/${}`, );
if ("refused" in ) throw new ((.));
const = ();
.("refused" in ? . : . === && ..());
.(());https://integraledger.com/lcp/a2a/legal-context/v1
true
true
{ refused: true, code: 'a2a/no-legal-context' }The LCP profile a2a-legal-context-v1 states the extension's rules.
Next
- Buyer: the gate every checkout on this page runs before the buyer approves.
- Pairings reference: each checkout pairing's pattern and what its record proves.
Last updated on