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:
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
The201 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.
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.