GET /v1/transactions (or your own ledger) and
render the list your way. What you don’t have to build is the receipt — the
authoritative, branded detail view for a single past payment, with its live
status (still pending, captured, failed, or refunded).
When a user taps a history row, fetch a short-lived receipt token from
Zennopay (one HMAC-signed call) and hand it to the SDK’s presentReceipt. The
SDK fetches the authoritative receipt from Zennopay, renders it natively, and —
if the payment is still settling — polls it to a terminal state in place.

The authoritative Zennopay receipt, reopened from a history row in the sample wallet app.
You own the list, we own the receipt. Your history list is yours — cached,
paginated, styled to your app. The receipt surface is the SDK’s: one call
reopens the same branded receipt your user saw at pay time, but refreshed to
the payment’s current state. No receipt UI, no status mapping, no refund copy
to build.
The flow
The receipt token is fetched on demand, only when the user opens a specific receipt — not ahead of time for the whole list.The receipt token
A receipt token is a distinct credential from the session token you use to make a payment. Like the session token, Zennopay mints it — you request one with a single HMAC-signed call toPOST /v1/payment_intents/{intent_id}/receipt_token (no
body). Its scope, power, and lifetime are different: a session token authorizes
one debit on one intent; a receipt token only reads a receipt.
How a receipt token differs from a session token:
- Minted per intent, scoped to a user. You mint it by naming an intent you
own; Zennopay derives the user (
sub) from that stored intent, so the token can open any of that user’s receipts within its window — but never another user’s. - Read-only. It authorizes
GET /receiptand nothing else — it can never confirm, cancel, or move money. - Reusable. The SDK reuses the one token to poll a pending receipt to its terminal state, so you don’t re-mint on every poll.
- No attestations. KYC/sanctions are a pay-time contract; reopening a receipt doesn’t move money, so they aren’t required.
Fetch the receipt token
Add one route to your session endpoint backend. It makes one HMAC-signed call to Zennopay — no body — for the intent id of the tapped row, then returns the token. Reuse the samehmacHeaders helper from
your session endpoint; no dependencies beyond node:crypto.
The reference
zennopay-partner-starter
(v0.2.0+) ships this route as POST /receipt-token { intent_id } → { receipt_token, expires_at }. Use it as the reference.Fetch is the SDK’s job
The SDK callsGET /v1/payment_intents/:id/receipt with the receipt token as
Authorization: Bearer. You usually don’t call this endpoint yourself —
presentReceipt does. It returns the authoritative receipt with a live
status (pending, captured, failed, or refunded) and the branded
receipt fields.
See GET /payment_intents/:id/receipt
for the full response shape.
Present the receipt
Each platform exposes onepresentReceipt call. Pass the intentId from your
history row and the freshly minted receiptToken; give it a refreshReceiptToken
hook so it can re-mint if the token expires while a slow payout is still being
polled.
- iOS
- Android
- Flutter
- React Native
Zennopay 0.3.0+What the SDK handles for you
- Pending → terminal polling. If the payment is still settling, the receipt
opens in a processing state and the SDK polls
GET /receipt(reusing the same token) until it reachescapturedorfailed, updating the receipt in place. A slow payout gets honest copy, not a silent spinner. - Refunded state. A
refundedreceipt renders refund messaging — the amount returned and when — instead of a plain success screen, so a user reopening an old payment sees the current truth. - Cross-user 404. A token for user A can’t open user B’s receipt: the
endpoint returns
404with no existence leak, and the SDK surfaces a not-found state rather than another user’s data. - Branding. The receipt honors the same
ZennopayAppearanceyou pass to the PaymentSheet, so a reopened receipt matches the one shown at pay time.
Next steps
Build your session endpoint
The receipt-token route lives alongside your session endpoint.
GET /payment_intents/:id/receipt
The authoritative receipt response shape and the no-leak 404.
Webhooks
Reconcile terminal states server-side, independently of the receipt.
PaymentSheet overview
The pay-time flow the receipt mirrors.