Quickstart
Your first test payment. No real money moves.
Seven steps, all in test mode through the built-in test gateway. You need a ZopPay account and a server that can make HTTPS requests.
Step 01
Create a test key
Sign up, open API keys in the dashboard and create a key: give it a name and choose the mode Test. Copy the secret straight away: it is shown once, and ZopPay keeps only a hash of it.
Keep the key on your server. Never put it in a web page or a mobile app.
Shell# Keep the key on your server. Every request sends it as # Authorization: Bearer $ZOPPAY_SECRET_KEY export ZOPPAY_SECRET_KEY="sk_test_…" # paste the whole key from Dashboard → API keys
Step 02
Create a payment
Send the amount in the currency’s smallest unit (paise for rupees:
49900is ₹499.00), the currency, your own order reference and thereturn_urlthe shopper should come back to. It must start withhttps://.Add an
Idempotency-Keyso that a retried request returns the first answer instead of creating a second payment.The answer is
201 Createdwith the payment underdata, including itscheckout_url.POST /v1/payments# Create a payment. Needs: export ZOPPAY_SECRET_KEY=sk_test_… curl https://api.zoppay.in/v1/payments \ -H "Authorization: Bearer $ZOPPAY_SECRET_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order_1042" \ -d '{ "amount": 49900, "currency": "INR", "reference": "order_1042", "return_url": "https://shop.example.com/orders/1042/complete", "customer": { "name": "Asha Rao", "email": "asha@example.com", "phone": "+919876543210" } }' # The answer is 201 Created, with the payment under "data". # Send the shopper's browser to data.checkout_url.
ResponseHTTP/1.1 201 Created { "data": { "id": "pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA", "status": "pending", "amount": 49900, "currency": "INR", "reference": "order_1042", "checkout_url": "https://checkout.zoppay.in/q3l7vz2kbx6ndw4rj5tmhs2pyc7ga4fe6uon3wia" } }
Step 03
Send the shopper to the checkout
Redirect the shopper’s browser to
checkout_url. The link works for 30 minutes.With a test key the checkout is a test page that says no real money moves. Press Pay (test mode). With a live key the shopper goes to your gateway’s own secure page instead.
Afterwards the browser comes back to your
return_urlwithpayment_idandstatusadded.Back on your site# After the checkout, the shopper's browser comes back to your return_url # with the payment id and status added to the query string: https://shop.example.com/orders/1042/complete?payment_id=pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA&pp_outcome=payment-success&status=captured # status is captured, failed or pending. Anyone can type this URL, # so confirm the payment from your server before you ship the order.
Step 04
Confirm the payment on your server
Anyone can type a return URL, so don’t ship an order on the query string alone. Fetch the payment with your secret key and check that
statusiscaptured.A key only sees payments of its own mode: a test key gets
404for a live payment.GET /v1/payments/{id}# Get a payment. Use the data.id that create returned. curl https://api.zoppay.in/v1/payments/pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA \ -H "Authorization: Bearer $ZOPPAY_SECRET_KEY" # The answer is 200 OK, with the payment under "data". # Fulfil the order when data.status is "captured".
Step 05
Receive webhooks
In the dashboard, open Webhooks, add your endpoint’s address and pick the events. The address must be
https://on a public host. Copy the signing secret shown when you save it.Check
X-PP-Signature-V2on every delivery before you trust it. To try your receiver on your own machine first, the cURL tab sends it a correctly signed test event.Verify a webhook# Send a correctly signed test event to your own receiver, the way ZopPay signs it. SECRET='whsec_…' # your endpoint's signing secret: the whole value, whsec_ included URL='http://localhost:3000/webhooks/zoppay' BODY='{"id":"whe_01JD3VC4R7T9W2Y5A8C1E3G6J9","type":"payment.captured","created_at":"2026-10-19T10:21:03.512Z","organisation_id":"org_01JD2Q0X8M4N6P1R3S5T7V9W2Y","data":{"amount":49900,"currency":"INR","gateway_transaction_id":"test_txn_pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA_1760869263512345678","mode":"test","order_id":"ord_01JD3V8Q2K5N7Q9S1V3W5Y7A0C","payment_id":"pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA","reference":"order_1042"}}' TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) # X-PP-Signature-V2 = "sha256=" + hex(HMAC-SHA256(secret, timestamp + "." + raw body)) SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)" curl -i "$URL" \ -H "Content-Type: application/json" \ -H "X-PP-Event: payment.captured" \ -H "X-PP-Timestamp: $TS" \ -H "X-PP-Signature-V2: $SIG" \ -d "$BODY"
Step 06
Try a refund
Refund part of the test payment, or leave out
amountto refund what is left. Test refunds finish at once with the statussucceeded, andrefund.succeededis sent to your webhook.With a live gateway a refund can stay
pendingfor a while; you hear the result by webhook.POST /v1/refunds# Refund ₹100.00 of a payment. Leave out "amount" to refund everything that is left. curl https://api.zoppay.in/v1/refunds \ -H "Authorization: Bearer $ZOPPAY_SECRET_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: refund_order_1042_1" \ -d '{ "payment_id": "pay_01JD3V8Q4M6Y2C9T7K5W1R0BNA", "amount": 10000, "reason": "Item returned" }' # The answer is 201 Created, with the refund under "data". # data.status is "succeeded", or "pending" until the gateway confirms.
Step 07
Go live
Your account goes live by itself once you have done three things in the dashboard: accept the terms, add billing details (email, address and a tax ID such as a GSTIN), and connect a live gateway. There is no review by ZopPay. Your gateway may have its own approval.
Then create a live key (
sk_live_…) and use it in place of the test key. The requests stay the same. Until you are live, a live payment answers403and the message says what is missing.A live key before going liveHTTP/1.1 403 Forbidden {"error":{"message":"live payments are unavailable until this organisation goes live: accept the terms, add billing details (email, address and tax id), connect a gateway"}}
Stuck on a step?
Send us the request you made and the response you got, and we will help you find the problem.