Skip to Content
DocsConceptsReceipts

Receipts

A receipt is the room’s signed attestation that a decision concluded. It’s a compact JWS (signed JWT) — a self-contained, cryptographically signed artifact that anyone holding the receipt + the room’s public key can verify offline, without calling the room.

The receipt is the binding artifact. The decision didn’t “happen” until a receipt is emitted.

Shape

A receipt is a compact JWS on the wire — three base64url-encoded segments separated by dots:

<base64url(header)>.<base64url(payload)>.<base64url(signature)>

Header:

{ "alg": "EdDSA", "typ": "grp-receipt+jwt", "kid": "room-key-2026" }

Payload (decoded):

{ "iss": "https://grp.app/api/rooms/k7m3pq2dn", "aud": "https://grp.app/api/rooms/k7m3pq2dn", "jti": "urn:uuid:0d1...", "iat": 1762880400, "grp": { "decision_id": "0197f3a2-...", "decision_kind": "standing", // "standing" | "ephemeral" | "conclusion" "room_id": "https://grp.app/api/rooms/k7m3pq2dn", "mechanism": { "kind": "simple_majority", "parameters": { "question": "Approve lobby renovation: $50K, 8-week project", "options": ["approve", "reject", "request_more_bids"] } }, "windows": { "deliberation_started_at": "2026-05-04T15:00:00.000Z", "voting_started_at": "2026-05-04T15:00:00.000Z", "voting_ended_at": "2026-05-04T16:00:00.000Z", "cooldown_ended_at": "2026-05-04T16:00:00.000Z" }, "deliberation_message_count": 6, "votes": [ // Full choice attribution. `mandate_ref` and `signature` are null for // token-cast choices (the URL-room baseline); rooms configured // choice_visibility: "never" omit per-choice rows entirely. { "agent_id": "did:web:1a.coop", "choice": "approve", "weight": 1, "cast_at": "2026-05-04T15:12:09.000Z", "mandate_ref": "urn:uuid:...", "signature": null }, { "agent_id": "participant:8f2c...", "choice": "approve", "weight": 1, "cast_at": "2026-05-04T15:14:41.000Z", "mandate_ref": null, "signature": null } // ... ], "overrides": [], // reserved — always empty at v0.1 "outcome": { "status": "completed", // completed | rejected | tied | no_quorum | canceled "winning_option": "approve", "tallies": { "approve": 10, "reject": 1, "request_more_bids": 1 }, "diagnostics": { "cast_votes": 12, "eligible_voters": 12 } }, "prev_hash": "sha256:def456...", "sequence": 42 } }

A room’s conclusion receipt (emitted by the close act) uses the same shape with decision_kind: "conclusion", empty votes/tallies, plus closing_statement — the only free text GRP ever signs — and concluded_by. Its prev_hash points at the final decision’s receipt, terminating the chain.

The payload is JCS-canonicalized (RFC 8785) before base64url-encoding — that makes the bytes deterministic across implementations, which is what makes the hash chain stable.

What standalone-verifiable means

To verify a receipt, you need:

  1. The receipt itself (the compact JWS string)
  2. The room’s JWKS (resolvable from the room’s .well-known/grp.json)

That’s it. You don’t need to call the room, and you don’t need to be online: the record proves it is exactly what the host signed and that its ballots produce its outcome.

The signature is Ed25519 over <base64url(header)>.<base64url(payload)> (standard JWS signing input per RFC 7515). The verifier finds the right key in the JWKS by matching the kid header.

Receipt chain

Every room maintains a hash chain of its receipts. Each receipt’s grp.prev_hash claim points to sha256:<hex> of the previous receipt’s compact-JWS bytes. This guards against:

  • Receipt deletion — gaps in the chain are detectable.
  • Receipt reordering — the prev-hash sequence is locked in.
  • Receipt forgery — a forged receipt with a wrong prev-hash breaks the chain.

The room publishes its current chain head; verifiers can fetch it to check that any receipt they hold is in the canonical chain.

Daily Merkle root publication

Operators SHOULD publish a daily Merkle root of each day’s receipts to a public audit log — Sigstore (the same append-only log used by npm package provenance, PyPI trusted publishing, GitHub Actions artifact attestations, and Kubernetes releases) is the reference choice. This adds a third independent gate: even if the room’s signing key is compromised, historical receipts are anchored in an external log no single operator controls. GRP Server Cloud has the publication path planned but not yet wired; receipts are fully standalone-verifiable without it.

Failure receipts

When a mechanism fails (quorum unmet, no winning option per the configured tie-break), the room still emits a receipt — a failure receipt — recording the failure mode, the inputs, and any partial state. The decision is on-record as having terminated; the receipt is the proof.

Cross-references

Last updated on