openapi.json
is callable here — request bodies are validated client-side against the
schema before they’re sent.
The playground talks to
https://api.sandbox.zennopay.com. Use a
sandbox HMAC key ({partner}_sandbox_*) — production keys are
rejected by the sandbox host.What you’ll need
- A sandbox HMAC key (
X-Zennopay-Key-Id) and shared secret. Compute the signature with the canonical-request recipe from Authentication. - For JWT-protected endpoints (
GET /v1/payment_intents/{intent_id}andPOST /v1/payment_intents/{intent_id}/confirm), a freshly minted partner JWT bound to the intent you’re calling. See Debugging auth for a tool that decodes your JWT so you can sanity-check the claims before trying it here.
Endpoints
The playground is generated from/openapi.json and groups
endpoints by tag:
- Payment Intents —
POST /v1/payment_intents,GET /v1/payment_intents/{intent_id},POST /v1/payment_intents/{intent_id}/confirm,POST /v1/payment_intents/{intent_id}/cancel
Open the OpenAPI spec
Raw OpenAPI 3.1 document. Drop this into Postman, Insomnia, or your own
codegen if you’d rather build a client locally.
Tips for first-time use
My HMAC request returns 401
My HMAC request returns 401
The most common causes, in order:
- Clock skew.
X-Zennopay-Timestampmust be within ±5 minutes of server time. NTP-sync your machine. - Body hash mismatch. The canonical request hashes the exact bytes of the request body. Don’t pretty-print or re-serialize between signing and sending.
- Source IP not allowlisted. The sandbox allowlist is configured per partner in the dashboard.
My JWT returns 401
My JWT returns 401
Decode the JWT with the JWT inspector
first and confirm:
audiszennopay-checkoutexp - iatis ≤ 600 secondszennopay:intent_idmatches the path parameter you’re callingzennopay:amount_usd_centsandzennopay:corridormatch the intent’s persisted valuesissis registered in the dashboard and its JWKS is reachable
My nonce keeps getting rejected
My nonce keeps getting rejected
Each
X-Zennopay-Nonce is single-use within a 10-minute window.
Generate a fresh random 32-byte hex string for every request — never
hardcode or reuse one.