# 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.

Source: https://immiscible.fly.dev/docs/concepts/receipts

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"
}
```

| Claim | Meaning |
|---|---|
| `iss` | the Immiscible deployment that issued it |
| `sub` | the agent |
| `act` | the action this receipt allows |
| `typ` | the action type, for example `payment` |
| `amt`, `cur` | the authorised amount in minor units, and its currency |
| `mer` | the merchant domain it was authorised for |
| `mdt` | the mandate it was allowed under |
| `hum` | `true` when a person approved this specific action, not only the mandate |
| `iat`, `exp` | issued and expiry times; a receipt is valid for five minutes |
| `jti` | a 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:

```bash
curl -X POST "https://immiscible.fly.dev/v1/verify" \
  -H "content-type: application/json" \
  -d '{ "receipt": "eyJhbGciOiJFZERTQSIs..." }'
```

Node:

```ts
const res = await fetch('https://immiscible.fly.dev/v1/verify', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ receipt }),
});
const { valid, claims, reason, replayed } = await res.json();
```

Python:

```python
import requests

res = requests.post("https://immiscible.fly.dev/v1/verify", json={"receipt": receipt})
result = res.json()
```

```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](https://immiscible.fly.dev/docs/security/verifying-offline.md#receipts).

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](https://immiscible.fly.dev/docs/guides/card-rail.md) 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.
