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

FieldDescription
receipt_idPublisher-assigned identifier. Globally unique within the publisher's domain.
action_idThe id of the action that ran, exactly as published in the manifest.
amount_msatsAmount paid, in millisatoshis. Must equal the price quoted at challenge time.
buyer_pubkeyOptional. Schnorr-secp256k1 pubkey the buyer supplied via the X-Agents402-Buyer-Pubkey request header. Required for the receipt to support verifiable feedback events.
payment_hashLightning payment hash for the BOLT11 invoice that was paid.
input_hashSHA-256 of the canonical JSON of the action input.
output_hashSHA-256 of the canonical JSON of the action output.
completed_atRFC 3339 timestamp at which the publisher finalized the receipt.
service_pubkeyEd25519 SPKI public key in hex. Must match the manifest's receipts.pubkey_hex.
signatureEd25519 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