Invite-only routing infrastructure for high-volume merchants & payment partners
24/7 Engineering Desk
How It Works Security Partners API Docs Talk to Sales

API Documentation

Everything you need to accept payments through Zoopay: create an order, redirect your customer to pay, and confirm the final status server-to-server. No login required to read this page — you'll need an API Key and Secret Key from your Merchant Dashboard to make live calls.

Introduction

The Zoopay API is a RESTful JSON API served over HTTPS. Every request and response body is JSON. There are currently two endpoints: one to create a payment order and one to check its status.

Zoopay onboarding is invite-only. If you don't have API credentials yet, contact us to get evaluated. Already onboarded? Sign in to your Merchant Dashboard and open Developers → API & Webhooks to copy your keys.

Base URL

https://test.zoopay.site

Authentication

Every request is authenticated with two credentials passed directly inside the JSON request body — an api_key and a secret_key. There is no separate auth header or token exchange step.

Security warning: your secret_key must never be embedded in client-side code (JavaScript, mobile apps) or committed to version control. Only ever call these endpoints from your own backend server.

Create Order

Initializes a payment and returns a checkout payment_url. Redirect your customer's browser to that URL to complete payment.

POST https://test.zoopay.site/api/create-order

Request Parameters

ParameterTypeRequiredDescription
api_keystringYesYour public API key.
secret_keystringYesYour secret key.
order_idstringYesYour own unique order reference.
amountdecimalYesTransaction amount in INR (e.g. 199.00).
redirect_urlstringYesAbsolute URL the customer returns to after paying.
customer_namestringNoDefaults to your account name if omitted.
customer_emailstringNoDefaults to your account email if omitted.
customer_phonestringNo10-digit mobile number.
Request Example
{
  "api_key": "pk_live_xxxxxxxx",
  "secret_key": "sk_live_xxxxxxxx",
  "order_id": "SYS_ORDER_1001",
  "amount": "199.00",
  "customer_name": "John Doe",
  "customer_email": "[email protected]",
  "customer_phone": "9876543210",
  "redirect_url": "https://yoursite.com/return"
}
Success Response
{
  "status": "success",
  "message": "Order initialized successfully.",
  "data": {
    "transaction": {
      "zoopay_txnid": "ZP260728A1B2C3D4",
      "order_id": "SYS_ORDER_1001",
      "status": "PENDING"
    },
    "financials": {
      "currency": "INR",
      "gross_amount": "199.00",
      "platform_fee": "5.97",
      "net_settlement_amount": "193.03"
    },
    "urls": {
      "payment_url": "https://test.zoopay.site/pay?id=ZP260728A1B2C3D4"
    }
  }
}

Check Status

Never rely solely on the customer returning to your redirect_url — they may close the tab before that happens. Always confirm the final outcome with this server-to-server call.

POST https://test.zoopay.site/api/check-status

Request Parameters

ParameterRequiredDescription
api_keyYesYour public API key.
secret_keyYesYour secret key.
order_idCond.Required if zoopay_txnid is not passed.
zoopay_txnidCond.Required if order_id is not passed.
Request Example
{
  "api_key": "pk_live_xxxxxxxx",
  "secret_key": "sk_live_xxxxxxxx",
  "order_id": "SYS_ORDER_1001"
}
Response
{
  "status": "success",
  "message": "Transaction details fetched successfully.",
  "data": {
    "transaction": {
      "zoopay_txnid": "ZP260728A1B2C3D4",
      "order_id": "SYS_ORDER_1001",
      "gateway_txnid": "UPI20260101123456",
      "payment_status": "SUCCESS"
    },
    "financials": {
      "currency": "INR",
      "gross_amount": "199.00",
      "platform_fee": "5.97",
      "net_settlement_amount": "193.03"
    },
    "customer": {
      "name": "John Doe",
      "email": "[email protected]",
      "phone": "9876543210"
    },
    "timeline": {
      "created_at": "2026-07-25 10:15:00",
      "updated_at": "2026-07-25 10:16:32"
    }
  }
}

Create Payout

Programmatically create a payout from your wallet balance to a named recipient - modeled exactly like a "Send Payout to User" submission on your dashboard (not a self-withdrawal), since an API call always names who the money is going to. Every request lands in our staff review queue as PENDING; there is no instant/auto-transfer via this API - a member of our team always reviews and releases it manually.

POST https://test.zoopay.site/api/create-payout
reference_id is your own idempotency key. Retrying a call with the same reference_id for your account always returns the original request instead of creating a duplicate payout - always send one, and generate a new one only for a genuinely new payout.
amount_type controls how amount is interpreted - the same two modes as the "I Know the Amount to Send" / "I Know the Amount to Receive" toggle on the dashboard. "send" means amount is the gross amount debited from your wallet; "receive" (the default for this API) means amount is the exact net amount the recipient should end up with, and we work out the gross for you.
For dest_type: "usdt", the final converted USDT amount (after payout charges) must currently be between 1,000 USDT and 10,000 USDT per request - a request outside this range returns a 400 error. These limits are set by the platform and can change.

Request Parameters

ParameterTypeRequiredDescription
api_keystringYesYour public API key.
secret_keystringYesYour secret key.
reference_idstringYesYour own unique reference for this payout (max 100 chars) - used for idempotent retries and to look it up later.
recipient_namestringYesWho this payout is being sent to (a customer, vendor, employee - or your own name/business if paying out to yourself).
amountdecimalYesInterpreted according to amount_type below (e.g. 5000.00).
amount_typestringNo"send" (gross, debited from wallet) or "receive" (net, what the recipient gets). Defaults to "receive".
dest_typestringNobank, upi, or usdt. Defaults to whichever destination you have configured (or provided below) if omitted.
bank_name / account_name / account_number / ifsc_codestringNoBank details for this payout only. Omit to use your saved default bank account instead.
upi_idstringNoUPI ID for this payout only. Omit to use your saved default UPI ID instead.
usdt_address / usdt_networkstringNoUSDT wallet for this payout only (only if USDT payouts are enabled on your account). Omit to use your saved default USDT wallet instead.

A destination provided here is used only for this one request - it never changes your saved dashboard default.

Request Example - "amount to receive" (default)
{
  "api_key": "pk_live_xxxxxxxx",
  "secret_key": "sk_live_xxxxxxxx",
  "reference_id": "PAYOUT_RUN_2026_08_01",
  "recipient_name": "John Doe",
  "amount_type": "receive",
  "amount": "5000.00",
  "dest_type": "bank",
  "bank_name": "ICICI Bank",
  "account_name": "John Doe",
  "account_number": "000123456789",
  "ifsc_code": "ICIC0001234"
}
Success Response
{
  "status": "success",
  "message": "Payout request created successfully.",
  "data": {
    "payout": {
      "payout_reference": "PYO260728A1B2C3D4",
      "reference_id": "PAYOUT_RUN_2026_08_01",
      "recipient_name": "John Doe",
      "status": "PENDING"
    },
    "financials": {
      "currency": "INR",
      "gross_amount": "5050.51",
      "platform_fee": "50.51",
      "net_amount": "5000.00",
      "usdt_amount": null,
      "usdt_rate": null
    },
    "destination": {
      "type": "BANK",
      "masked_detail": "AC ****6789"
    }
  }
}

Payout Status

Look up a payout's current status by either identifier. Prefer Payout Webhooks for real-time updates and use this endpoint to poll only as a fallback or for reconciliation.

POST https://test.zoopay.site/api/payout-status

Request Parameters

ParameterRequiredDescription
api_keyYesYour public API key.
secret_keyYesYour secret key.
payout_referenceCond.Required if reference_id is not passed.
reference_idCond.Required if payout_reference is not passed.
Request Example
{
  "api_key": "pk_live_xxxxxxxx",
  "secret_key": "sk_live_xxxxxxxx",
  "reference_id": "PAYOUT_RUN_2026_08_01"
}
Response
{
  "status": "success",
  "message": "Payout details fetched successfully.",
  "data": {
    "payout": {
      "payout_reference": "PYO260728A1B2C3D4",
      "reference_id": "PAYOUT_RUN_2026_08_01",
      "recipient_name": "John Doe",
      "status": "COMPLETED",
      "utr_number": "N2830200445",
      "admin_remark": null
    },
    "financials": {
      "currency": "INR",
      "gross_amount": "5000.00",
      "platform_fee": "50.00",
      "net_amount": "4950.00"
    },
    "destination": {
      "type": "BANK",
      "masked_detail": "AC ****6789"
    },
    "timeline": {
      "requested_at": "2026-08-01 09:00:00",
      "processed_at": "2026-08-01 14:22:10"
    }
  }
}

status is PENDING, COMPLETED, or FAILED. admin_remark carries the rejection reason on a FAILED payout; processed_at is null until it's approved or rejected.

Error Handling

Both endpoints share the same error shape. Validation failures and authentication errors return HTTP 400; a maintenance window returns HTTP 503.

400 Bad Request
{
  "status": "error",
  "message": "Authentication Error: Invalid API credentials or account suspended."
}
503 Service Unavailable
{
  "status": "error",
  "message": "Service Unavailable: The payment engine is currently undergoing scheduled maintenance. Please try again later."
}

Webhooks (Server Notifications)

Zoopay pushes a real-time HTTP POST to your Callback URL the moment a transaction settles as SUCCESS or FAILED, so your system stays in sync even if the customer closes the browser before redirecting.

Set your Callback URL from your Merchant Dashboard under Developer Settings → Webhook Configuration. A Send Test Webhook button there fires a real signed request to your endpoint so you can confirm your handler works before going live.

Events

EventFired When
payment.successThe transaction completes successfully.
payment.failedThe transaction is declined, fails, or times out.
payout.completedOur team approves a payout request (see Create Payout) and releases the funds.
payout.failedOur team rejects a payout request - admin_remark in the payload carries the reason.

Payload - payment.success / payment.failed

POST Body
{
  "event": "payment.success",
  "zoopay_txnid": "ZP260728A1B2C3D4",
  "order_id": "SYS_ORDER_1001",
  "status": "success",
  "amount": "199.00",
  "net_amount": "193.03",
  "currency": "INR",
  "utr_number": "N2830200445",
  "customer_name": "John Doe",
  "customer_email": "[email protected]",
  "customer_phone": "9876543210",
  "created_at": "2026-07-25 10:15:00",
  "event_time": "2026-07-25T10:16:32+05:30"
}

status is "failed" and utr_number/net_amount are typically null on a payment.failed event. order_id is your own reference from Create Order; zoopay_txnid is ours.

Payload - payout.completed / payout.failed

POST Body
{
  "event": "payout.completed",
  "payout_reference": "PYO260728A1B2C3D4",
  "reference_id": "PAYOUT_RUN_2026_08_01",
  "settlement_id": 4821,
  "recipient_name": "John Doe",
  "status": "completed",
  "amount": "5000.00",
  "net_amount": "4950.00",
  "currency": "INR",
  "utr_number": "N2830200445",
  "admin_remark": null,
  "created_at": "2026-08-01 09:00:00",
  "event_time": "2026-08-01T14:22:10+05:30"
}

On payout.failed, status is "failed", utr_number/net_amount are null, and admin_remark carries the rejection reason. reference_id and payout_reference are only populated for payouts created via the Create Payout API - both are null for a withdrawal submitted from the merchant's dashboard.

Full Example: Verify, Process & Respond

Every request carries an X-ZooPay-Signature header — an HMAC-SHA256 hash of the raw request body, signed with your secret_key. Verify it before trusting the payload, so a request sent to a guessed or leaked webhook URL can't be spoofed. Once verified, respond first, then do slow work — don't make ZooPay (or a retry) wait on your emails/fulfillment logic.

PHP
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_ZOOPAY_SIGNATURE'] ?? ''; // "sha256=<hex>"
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret_key);

if (!hash_equals($expected, $header)) {
    http_response_code(401);
    exit; // unverified - reject, do not process
}

$data = json_decode($payload, true);

// Look up the order in your own system, e.g. by $data['order_id'], and update its status
// based on $data['status'] ('success' or 'failed'). Keep this part fast - a slow DB write
// here delays the response below, and a response that's too slow looks like a failed
// delivery and gets retried.

// This is the answer to "how do I return for the webhook": respond with a plain 2xx.
// ZooPay only checks the status code, not the body - "OK" is enough, and nothing more
// is required. Skip this and ZooPay sees it as failed and retries with backoff (see
// Retries below), which would notify you again for the same event.
http_response_code(200);
echo 'OK';

// Do slow work (emails, fulfillment, third-party API calls) AFTER responding, not before -
// e.g. queue a background job here rather than running it inline.

Retries

If your endpoint doesn't respond with an HTTP 2xx within 10 seconds, delivery is retried with exponential backoff — roughly 1 min, 5 min, 15 min, 30 min, 1 hr, 3 hr, 6 hr, then 12 hr — up to 8 attempts spanning about 24 hours, before being marked failed. Deliveries are independent per event, so a slow or briefly-down endpoint never blocks other notifications.

How to Process

  1. Verify the X-ZooPay-Signature header before trusting the payload (see above).
  2. Look up the transaction in your system using order_id or zoopay_txnid.
  3. Treat this as an accelerator, not your only source of truth — Check Status is still the authoritative call if you ever need to double-check.
  4. Return an HTTP 200 OK quickly. Do any slow processing (emails, fulfillment) after responding, not before — a slow handler risks a retry sending you a duplicate notification for the same event.