Receipts
A receipt is the audit primitive Lipafy emits on every successful capability call. It’s a signed JSON blob saying: “at time T, account A called capability C for amount X, and the upstream returned 2xx.” Anyone with the receipt and Lipafy’s public key can verify it — without needing access to the Lipafy database.
Why signed (not just logged)
A plain log line is fine for internal monitoring but useless as evidence to a third party — they have to trust Lipafy that the log wasn’t edited. A signed receipt is independently verifiable: take the receipt, take the public key from the docs, run the signature check yourself. You don’t need to trust us.
Use cases:
- Dispute resolution: A user claims they didn’t authorize a charge. The receipt shows which credential authorized it, what it called, and when.
- Provider settlement audit: A provider checks that the revenue Lipafy paid out matches the receipts they accumulated.
- Compliance: AP2-style mandate fulfillments need verifiable records linking the user’s signed mandate to the actual purchase.
What’s in a receipt
Fields:
capability_request_id— the same id asX-Payment-Referencereturned in the gateway response.grant_id— which authorization spent the money (API key grant, OAuth grant, or account_default grant).upstream_status— confirms it was a 2xx success.wallet_balance_after— the available balance after the debit landed. Useful for clients reconciling without a separate balance call.signature— Ed25519 over a canonical JSON serialization of everything above.key_idrotates quarterly; the active and historical public keys are published at the receipts API root.
Fetching a receipt
By id (capability_request_id):
Returns the receipt as JSON. Visible to the owner of the call.
The receipt is also reachable from the URL in the X-Payment-Receipt response header on a gateway call.
Verifying a receipt
You don’t need a Lipafy credential to verify — that’s the point. Use the public endpoint:
Returns:
If the signature doesn’t verify (tampered receipt or wrong key), valid: false plus a reason. The endpoint is rate-limited but unauthenticated — fine for external auditors.
Verifying offline
Offline verification depends on the public key for the receipt’s signature.key_id. Today, the supported public verification path is POST /v1/receipts/verify; use that endpoint when you need a stable external check.
Receipts vs. capability_requests
The capability_requests table is the mutable internal record. The receipt is a snapshot at success time of the immutable fields plus the signature. If a refund is later issued, the capability_request row gains a refunded_at field and a corresponding refund transaction — but the original receipt still validates. A receipt being valid does not mean “the charge wasn’t refunded”; it means “the charge happened.” Always check both the receipt and the request’s current status when reconciling.
Refunds
When a successful call needs to be reversed:
Allowed for:
- The account that paid (within the refund window — currently 7 days post-call).
- Lipafy support can assist with exceptional refund cases.
The refund:
- Credits the caller’s wallet (
refundtransaction). - Debits the provider’s internal balance.
- Marks the
capability_requestasrefunded. - Does not invalidate the original receipt — that signed fact stays true. A consumer reconciling sees both: receipt for the charge, refund line in the ledger.
Retention
Receipts are retained indefinitely on the platform side — they’re cheap (small JSON), auditable forever, and there’s no PII pressure to delete them. Today, fetch a known receipt by capability request id or use the X-Payment-Receipt header returned from the gateway.
Related
- Gateway flow — where receipts are generated
- Settlements — receipts are the per-call ledger that feeds period rollup
