Specification
Receipt format
Wire-level reference for agents402 receipts. Receipts are produced by the publisher on a successful paid action and signed with the publisher's service key.
JSON schema
agents402-receipt-v0.1.schema.json
json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"receipt_id", "action_id", "amount_msats", "payment_hash",
"input_hash", "output_hash", "completed_at",
"service_pubkey", "signature"
],
"properties": {
"receipt_id": { "type": "string", "pattern": "^rcpt_[A-Za-z0-9_-]+$" },
"action_id": { "type": "string" },
"amount_msats": { "type": "integer", "minimum": 0 },
"buyer_pubkey": { "type": "string", "pattern": "^[0-9a-f]{64}$",
"description": "Optional. Schnorr-secp256k1 pubkey supplied by the buyer at payment time so they can later publish verifiable feedback events." },
"payment_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
"input_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
"output_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
"completed_at": { "type": "string", "format": "date-time" },
"service_pubkey": { "type": "string", "pattern": "^[0-9a-f]+$" },
"signature": { "type": "string", "pattern": "^[0-9a-f]+$" }
}
}Field reference
| Field | Description |
|---|---|
receipt_id | Publisher-assigned identifier. Globally unique within the publisher's domain. |
action_id | The id of the action that ran, exactly as published in the manifest. |
amount_msats | Amount paid, in millisatoshis. Must equal the price quoted at challenge time. |
buyer_pubkey | Optional. Schnorr-secp256k1 pubkey the buyer supplied via the X-Agents402-Buyer-Pubkey request header. Required for the receipt to support verifiable feedback events. |
payment_hash | Lightning payment hash for the BOLT11 invoice that was paid. |
input_hash | SHA-256 of the canonical JSON of the action input. |
output_hash | SHA-256 of the canonical JSON of the action output. |
completed_at | RFC 3339 timestamp at which the publisher finalized the receipt. |
service_pubkey | Ed25519 SPKI public key in hex. Must match the manifest's receipts.pubkey_hex. |
signature | Ed25519 signature over the canonical-JSON encoding of every other field. |
Canonical form
Receipt signing canonicalizes the core fields in fixed alphabetical key order with absent optional fields (e.g. buyer_pubkey) omitted entirely:
text
order: action_id, amount_msats, buyer_pubkey?, completed_at,
input_hash, output_hash, payment_hash, receipt_id, service_pubkey
JSON.stringify(receipt, [<keys present, in the order above>])This is a deliberate subset of RFC 8785 (JCS) — no whitespace, fixed key order, UTF-8 — chosen for simplicity over universality. Adding a key to the schema never invalidates older signatures because the present-keys array is computed at sign time.
Signing & verifying
signing — Node.js
ts
function signReceipt(core, privateKey) {
const msg = Buffer.from(canonicalJSON(core));
return crypto.sign(null, msg, privateKey).toString("hex");
}verifying — Node.js
ts
function verifyReceipt(receipt, expectedPubkeyHex) {
if (receipt.service_pubkey !== expectedPubkeyHex) return false;
const { signature, ...core } = receipt;
const pub = crypto.createPublicKey({
key: Buffer.from(receipt.service_pubkey, "hex"),
format: "der", type: "spki",
});
return crypto.verify(null, Buffer.from(canonicalJSON(core)), pub,
Buffer.from(signature, "hex"));
}- ·Verification is fully offline — no network calls required.
- ·An invalid signature on an otherwise-shaped receipt MUST be rejected.
- ·A receipt's
service_pubkeymust match the manifest's published key at the time the receipt was issued. Pubkey rotation handling is up to the agent's reputation logic.
Next
Examples
Reference implementations of publishers, agents, and receipts.
agents402.org / 2026
Open protocol · v0.1