Skip to main content
Every PaymentSheet presentation starts with one round-trip to your backend. Your app asks for a “checkout session”; your server makes one HMAC-signed call to Zennopay and returns the pair the SDK needs. There is nothing to sign but the API call itself: you create a payment intent server-to-server, and Zennopay mints the session token and returns it in the same response. No RS256 key pair, no iss/kid, no JWKS — one credential, the HMAC secret. The SDK never sees your HMAC secret. It holds only the session token — a credential Zennopay minted, scoped to a single intent, that expires in a few minutes.

What you need

From the Zennopay Console → Developers tab:
No signing key pair. There is no partner-held RSA keypair and nothing to publish at a JWKS endpoint — Zennopay mints the session token.

Step 1 — Create the payment intent

POST /v1/payment_intents, HMAC-signed (full signing spec: Authentication → Server-to-server HMAC). The body carries your opaque user ID, the authorized USD amount in cents, the corridor, and your per-payment KYC + sanctions attestations:
partner_user_id must be your internal, opaque identifier — never a raw government ID (passport number, national ID). Zennopay enforces per-user regulatory limits against this ID (Vietnam: ₫5,000,000/transaction, ₫10,000,000/day, ₫25,000,000/month), so it must be stable per user.
Send an Idempotency-Key header (a UUID) so a network retry can’t create two intents.
Why attestations are in the create body. You own KYC and sanctions screening for your users; Zennopay owns them for merchants. Because Zennopay mints the session token, the attestation travels with the signed create call — explicit, per-payment, and auditable. Zennopay will not move money on an intent that doesn’t carry them. id_type / id_country declare which government ID your KYC bound this user to; the raw ID number itself never crosses.

Step 2 — Return the session token Zennopay minted

The 201 response carries the session token. There is no minting step on your side — read it off the response and hand it to your app:

Reference implementation (Node.js)

A complete Express route, modeled on the reference partner backend (zennopay-partner-starter v0.2.0+, HMAC-only). No dependencies beyond node:crypto.
The refresh route is what your app’s refreshSession hook calls when the SDK hits a 401 mid-flow: same intent, a fresh token from POST /v1/payment_intents/:id/session, fresh expiry window. Zennopay preserves the scan/quote/confirm state across the re-mint.

Security notes

  • Short-lived, single-use, intent-bound. If a session token leaks (device log, crash report), it authorizes at most one confirm, on one intent, for a few minutes — and the SDK never puts it in a URL, so it can’t leak via history or referrers.
  • Zennopay-minted. You never hold a session-signing key, so there’s no private key to protect, rotate, or leak. The token is opaque to you.
  • The HMAC secret stays server-side. Only HMAC-signed, server-to-server calls can create intents or re-mint tokens. The mobile app can only do what the session token allows.
  • Attest after auth, not before. Only create an intent for a user who is logged in, KYC-verified, and sanctions-screened right now — the attestations you send are per-payment statements, not cached facts.

Receipt tokens

To let a user reopen the authoritative receipt for a past payment — its live status and the Zennopay brand — you fetch a second, distinct credential: a receipt token. Like the session token, Zennopay mints it; you request it with one more HMAC-signed call. Request one on demand when the user taps a history row (you pass that row’s intent id; Zennopay derives the user from the stored intent). The reference zennopay-partner-starter (v0.2.0+) ships the route as POST /receipt-token { intent_id } → { receipt_token, expires_at }. The full flow — request, presentReceipt, pending-poll, refunded state, and the cross-user 404 — is in Reopen a receipt.

Next steps

Present the PaymentSheet

Hand the session to the SDK on iOS, Android, Flutter, or React Native.

Reopen a receipt

Fetch a receipt token and reopen the authoritative receipt for a past payment.

Test your integration

Drive the whole flow in the sandbox before going live.

Authentication

The full HMAC signing spec and the session-token contract.

Per-user limits

The corridor limits enforced against your partner_user_id.