VetoVetoDocs
Concepts

Receipts

On accept, the merchant issues an Ed25519-signed receipt that links back to the buyer's mandate. Two signed artifacts pointing at each other are your dispute evidence — verifiable offline, by anyone.

On accepted, the merchant issues an Ed25519-signed compact JWS receipt. Its mandateRef points back at the buyer's mandate subject. The buyer holds a mandate; the merchant holds a receipt that references it — two signed artifacts that point at each other are your non-repudiation / anti-chargeback evidence.

The claims

interface MerchantReceiptClaims {
  iss: 'veto-checkout';
  sub: string;              // receipt id
  sessionId: string;
  iat: number;
  merchantId: string;
  agentId: string;
  decision: 'accept' | 'reject' | 'hold';
  total: CartTotal;
  reasonCodes: string[];
  mandateType: 'veto' | 'ap2' | 'acp' | 'none';
  mandateRef?: string;      // ← the buyer mandate subject
  policyHash: string;       // names the exact policy version that decided
  inputFingerprint: string;
}

The policyHash is a canonical-JSON SHA-256 of the active policy, so a receipt provably names the policy version that produced it.

Verify offline, by anyone

Receipts verify against the merchant's public key — no call back to Veto required:

// From your own handle:
const result = checkout.verifyReceipt(receiptJws);
// → { valid, claims, reasonCodes }

// Or from the published key, anywhere:
import { verifyMerchantReceipt } from '@veto-protocol/checkout';
const jwks = await fetch('https://shop.acme.example/.well-known/jwks.json').then(r => r.json());
verifyMerchantReceipt(receiptJws, jwks);

Publish your verifying key so buyers can check receipts independently:

checkout.receiptJwks(); // { keys: [Ed25519Jwk] } — serve at /.well-known/jwks.json

Non-custodial by design.

The only key Veto holds signs receipts, not value. Funds settle directly to your own receiving address via the rail.

See the receipt verification guide for the failure modes (RECEIPT_INVALID_SIGNATURE, RECEIPT_WRONG_ISSUER, …).