Skip to content

Concepts

Receipts

Every allowed action carries a short signed token that says which agent was allowed to do what, under which mandate, and whether a person approved it. Anyone can check it, online or offline.

A receipt travels with the action. The agent can hand it to a merchant; the card rail spends it; an auditor can check it years later against the ledger.

#What a receipt is

A compact JWS, signed with Ed25519. The header:

JSON
{ "alg": "EdDSA", "kid": "k_2026_09", "typ": "assay-receipt+jwt" }

The claims:

JSON
{
  "iss": "https://immiscible.fly.dev",
  "sub": "agt_4f2c91a7",
  "act": "act_7Qm2c1f0",
  "typ": "payment",
  "amt": 4200,
  "cur": "GBP",
  "mer": "grocer.example",
  "mdt": "mdt_91c3e0b2",
  "hum": false,
  "iat": 1790444901,
  "exp": 1790445201,
  "jti": "r_2b7e4c"
}
ClaimMeaning
issthe Immiscible deployment that issued it
subthe agent
actthe action this receipt allows
typthe action type, for example payment
amt, curthe authorised amount in minor units, and its currency
merthe merchant domain it was authorised for
mdtthe mandate it was allowed under
humtrue when a person approved this specific action, not only the mandate
iat, expissued and expiry times; a receipt is valid for five minutes
jtia unique id; a receipt is single use

A receipt names no person. It carries pseudonymous ids only the issuing workspace can resolve.

#Verifying online

No account and no key are needed:

curl -X POST "https://immiscible.fly.dev/v1/verify" \
  -H "content-type: application/json" \
  -d '{ "receipt": "eyJhbGciOiJFZERTQSIs..." }'
JSON
{
  "valid": true,
  "claims": {
    "iss": "https://immiscible.fly.dev", "sub": "agt_4f2c91a7", "act": "act_7Qm2c1f0", "typ": "payment",
    "amt": 4200, "cur": "GBP", "mer": "ocado.com", "mdt": "mdt_91c3e0b2", "hum": false,
    "iat": 1791209943, "exp": 1791210243, "jti": "w0JBpe6jFQ6NA-RbE0MrgUDQ"
  }
}

A valid receipt has no reason. Online verification marks the receipt as seen, so a second check of the same receipt returns "valid": false with "replayed": true. valid: false always comes with a reason in plain English: a bad signature, an unknown key, an expired receipt ("expired": true), a wrong issuer, a withdrawn receipt ("revoked": true), a charge it does not cover ("mismatch": true, when you send expect) or a replay.

#Verifying offline

The public keys are a JWKS at https://immiscible.fly.dev/.well-known/immiscible-keys.json. Fetch it, keep it, and check receipts without calling Immiscible at all. The whole verifier in Node 22, using only node:crypto, is in verifying offline.

Offline verification cannot see replays. If single use matters to you (for payments it should), keep the jti values you have seen in the last five minutes, or call /v1/verify once per order.

#What a receipt proves, and what it does not

It proves that Immiscible, at iat, allowed this agent this action under a mandate a person created, and, when hum is true, that a signed-in person approved this action specifically.

It does not prove who the person is, that a payment will clear, or that the goods are what the agent described. It is not a payment guarantee and it does not move chargeback liability. It is evidence of authority, to sit alongside the card network’s own checks.

#Receipts at the card

With the card rail connected, the receipt is not only evidence, it is the key: a card bound to an agent authorises only against an unused receipt for the same payee and currency that covers the amount, and the authorisation uses it up.

#With agent payment protocols

Agent payment protocols are converging on the same idea, a signed statement of what a person authorised: Google’s Agent Payments Protocol (AP2), OpenAI and Stripe’s Agentic Commerce Protocol (ACP), Visa’s Trusted Agent Protocol and Mastercard’s Agent Pay. An Immiscible receipt is designed to travel alongside those, as an extra field carrying the independent record of what the person’s own rules allowed. We do not claim certification by, or membership of, any of these schemes.