Sandbox & testing
How pk_test keys and the simulated chain work, simulating payments from the console and testing your webhook handler end-to-end.
The sandbox lets you run the entire integration — invoice, checkout, webhooks, signature verification — without a single real transaction.
How the sandbox works
- A
pk_test_…key marks every invoice it creates as sandbox. - Sandbox invoices ride a simulated chain: the contract (endpoints, payloads, signatures, statuses) is byte-identical to production, but payments confirm in seconds and no blockchain is involved.
- Data is fully isolated: test keys see only sandbox invoices and events; live keys never see them. Idempotency keys are scoped per mode, so the same key string is safe in both.
- Sandbox limits are lower: 10 invoice creations and 90 reads per minute.
Base URL: the production API https://api.zerokyc-payments.com accepts pk_test_… keys directly — this is the recommended sandbox. Live keys run real payments on the same API.
Simulating payments
Open the invoice in the console (Invoices → the invoice) and press Simulate payment: the mock chain fabricates a matching transaction and runs detection immediately — you'll see the status flip to confirmed and receive the real payment.detected → payment.confirmed webhooks, signed exactly like production ones.
Other states:
underpaid— the simulate API accepts a customamount; a value below the due amount produces a realpayment.underpaidwebhook (invoke it from your console session, e.g. from the browser devtools againstPOST /console/v1/mock/simulate-payment).expired— create an invoice withttl_minutes: 10(the minimum) and wait, or simply test the handler with a fabricated payload during development.
Testing your webhook handler
- Endpoint test event. In Webhooks, add your endpoint and press Send test event — a signed
webhook.testarrives within seconds. If it shows as delivered, the URL, TLS and 2xx-fast rule all work. - Local receiver. During development, print what we send:
# receiver.py — minimal webhook sink for local testing
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers["Content-Length"]))
print("path:", self.path)
for h in ("X-ZKP-Signature", "X-ZKP-Event-Id", "X-ZKP-Event-Type"):
print(f"{h}: {self.headers[h]}")
print("body:", body.decode())
self.send_response(200); self.end_headers()
HTTPServer(("0.0.0.0", 9000), H).serve_forever()
The API must reach your handler over HTTPS — use a tunnel (cloudflared, ngrok) for local development. Verify the signature in testing too: an unverified handler in test usually stays unverified in production.
- Full cycle. Create a sandbox invoice → Simulate payment → confirm your handler verified the signature, deduplicated on
event.idand reacted only topayment.confirmed.
Going live
- Create a
pk_live_…key in the console (API keys); issuing live keys requires a TOTP step-up — the key is shown once, store it in your secret manager. - Point the integration at the same
https://api.zerokyc-payments.com; only the key changes. - Keep webhook signature verification on — production events differ from sandbox only in timing (real confirmations take their network's time).
- Watch
429/Retry-Afterand the error envelope: Errors, Rate limits & idempotency. - Do a first small live invoice end-to-end before turning on real traffic.