Getting Started

Introduction

What this platform does for you

Our payment orchestration platform gives you a single API to collect payments from your customers and disburse funds via payouts, across payment modes, with reliable balances and reports.

You integrate once. We handle the rest — processing selection, retries, reconciliation, settlement tracking, chargebacks, and compliance reporting.

What you get
  • Single API surfacePOST /orders to collect, POST /payouts to disburse. Same for UPI, cards, netbanking, IMPS.
  • Reliable delivery — every state change of every order/payout is delivered to your webhook, at-least-once, in version order.
  • Accurate balances — always up-to-date, always explainable by transaction history.
  • Settlement transparency — see exactly what settled, when, and what commission and reserve applied per transaction.
  • Reserve tracking — rolling reserves tracked per order, released on maturity, and visible in real time.
  • Chargeback handling — chargebacks land in your dashboard and your reports with reason and cost breakdown.
  • Ready-to-file reports — daily settlement reports, monthly GST invoices, chargeback reports, payout reports.
  • Sandbox — test everything with mock money before going live.
What we don’t do (yet)
  • End-user checkout UI. You build the payment page; we handle the server-side.
  • Cross-border / multi-currency. INR only in v1.
  • Credit or lending.
  • Merchant-initiated refunds. Refunds today are handled by us based on our rules.
Who this is for

This documentation is for:

  • Developers integrating our APIs into your product.
  • Finance and ops users consuming reports for reconciliation and month-end close.
  • Compliance users needing audit trails.
What you’ll do to get started
  1. Set up your account — sign the agreement, get onboarded.
  2. Get API credentials — API key + IP allowlist.
  3. Integrate in sandbox — build against test credentials.
  4. Go live in production — flip a switch, real merchants, real money.
How to read these docs
  • Start with Getting Started if you’re new.
  • Concepts explain how the platform thinks about money, orders, balances. Read these once — they save you many “why does X do Y” questions later.
  • API reference is the day-to-day integration surface.
  • Reports documents every report we publish to you.
  • Support for when you’re stuck.
Support

If anything is unclear or missing, contact us — see Support. Documentation gaps are bugs; please report them.

Getting Started

Account Setup

When you need this

Before you write a single line of integration code. This walks you through everything from initial signup to being ready to hit sandbox APIs.

The onboarding steps
1. Commercial agreement

Signed with our business team. Covers: - Fee structure (platform commission, GST, chargeback penalty) - SLA (uptime, response time, settlement cadence) - Data processing agreement (DPA) - Support tier - Term and termination

You should have a signed copy before proceeding to the next steps.

2. KYC and compliance

Provide the following: - Business registration document (Certificate of Incorporation / Partnership Deed / etc.) - GSTIN and PAN - Authorized signatory KYC (PAN + Aadhaar or equivalent) - Bank account proof (cancelled cheque or bank letter) - Nature of business + expected transaction volume

Our compliance team completes review within 5 business days typically.

3. Technical readiness checklist

Before we issue credentials, you need to have:

  • Callback URL — a publicly-reachable HTTPS endpoint where we send you webhooks. Must respond with 2xx within 10 seconds. See Webhooks.
  • Egress IPs — the source IPs from which your servers will call our APIs. We allowlist these for security.
  • Technical contact — one person on your side who owns the integration.
4. Credentials issued

Once above is complete, we provision:

  • Sandbox API key — for testing
  • Merchant ID (X-Client-Id) — your identifier
  • Production API key — provided after sandbox integration is validated by both sides

Credentials are shared over a secure channel (encrypted email or password manager). Never over Slack, WhatsApp, or plaintext email.

5. Fee configuration seeded

Based on your commercial agreement, we set up your per-payment-mode fee configuration internally. You can view (read-only) via the dashboard or by request.

6. Sandbox integration

Build against sandbox using the API key. See Environments for the sandbox base URL.

7. Sandbox certification

We do a joint test with you to verify: - Order creation works end-to-end - Payouts complete - Webhooks reach you and you process them correctly - Retry / idempotency behaviour on your side is safe

8. Go-live

Production API key issued. Your production callback URL and IPs are allowlisted. You go live.

Onboarding timeline (typical)
Step Duration
Commercial agreement 1–2 weeks
KYC review 3–5 business days
Sandbox credentials issued Same day after KYC clearance
Sandbox integration 1–2 weeks (depends on your team)
Certification 2–3 days
Production go-live Same day after certification
What we need from you at each step

We’ll email you a checklist during onboarding. To move fast, have these ready:

  • Signed agreement
  • KYC documents (see step 2)
  • Callback URL (staging + production)
  • Egress IPs (staging + production)
  • Technical contact
  • Ops contact (for reports and reconciliation)
After go-live
  • Access to the merchant dashboard.
  • Support channels active. See Support.
Companion docs
Getting Started

Authentication

When you need this

Every API call to the platform. This doc explains what credentials you get, how to send them, and how to keep them safe.

What you need on every request

Two required headers:

X-Client-Id: <your merchant id>
X-Api-Key:   <your API key>

Plus IP allowlisting — requests are only accepted from IPs we’ve whitelisted for your account.

Example
curl -X POST <PLATFORM_BASE_URL>/api/v1/orders \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{ "clientOrderId": "order-001", "amount": 500.00, "currency": "INR", "paymentMode": "UPI" }'
Credential types
Merchant ID (X-Client-Id)

Your unique identifier. Shown in every dashboard URL. Not secret — but not to be shared publicly.

Format: merchant_<random_id>.

API key (X-Api-Key)

The secret you use to authenticate. Treat like a password.

Format: sk_<env>_<random> where <env> is test (sandbox) or live (production).

You may have multiple API keys per account (rotation, per- integration, etc.).

Where credentials come from
  • Sandbox keys — issued at onboarding.
  • Production keys — issued after sandbox certification.
  • Additional keys — created any time via the merchant dashboard (Settings → API Keys) or by contacting support.
Rotating API keys

Rotate keys every 90 days as best practice, or immediately if compromised.

Zero-downtime rotation:

  1. Create a new key in the dashboard.
  2. Deploy it to your servers alongside the old key.
  3. Verify traffic is flowing on the new key (dashboard shows per-key usage).
  4. Revoke the old key.

Old keys can be revoked without downtime.

IP allowlist

Every API call is checked against your account’s IP allowlist. A call from a non-allowlisted IP returns:

HTTP 403 Forbidden
{ "errorCode": "IP_NOT_ALLOWED", "errorMessage": "Source IP not on account allowlist" }
Adding IPs
  • Dashboard → Settings → IP Allowlist → Add IP or CIDR.
  • Or contact support.

Add both individual IPs and CIDR ranges: - 203.0.113.42 (single IP) - 203.0.113.0/24 (range)

We recommend individual IPs where possible. CIDR ranges expand your exposure surface.

Sandbox vs production

Separate allowlists per environment. Adding an IP to sandbox does not add it to production.

Security best practices
  1. Never commit API keys to source control. Use environment variables, secret managers, or vault services.
  2. Never expose API keys to browsers or mobile apps. All calls should go from your backend server.
  3. Rotate keys on a schedule. Every 90 days minimum.
  4. Rotate immediately if a key may have been exposed (accidentally logged, leaked in a screenshot, employee left the company).
  5. Use different keys per environment. Never reuse a production key in staging or vice versa.
  6. Restrict IP allowlist to the specific servers that need access. No 0.0.0.0/0.
  7. Monitor key usage. Dashboard shows per-key request rate; a sudden spike or drop is worth investigating.
HTTP Error code What went wrong
401 UNAUTHORIZED Missing or invalid X-Api-Key
401 CLIENT_ID_MISMATCH X-Client-Id doesn’t match the key
403 IP_NOT_ALLOWED Source IP not on allowlist
403 KEY_REVOKED Key has been revoked
403 ACCOUNT_DISABLED Account is disabled — contact support
Companion docs
Getting Started

Environments

When you need this

Before your first API call. Choose the right environment for what you’re doing.

Two environments
Environment Base URL Money Purpose
Sandbox <SANDBOX_BASE_URL> Fake Integration + testing
Production <PRODUCTION_BASE_URL> Real Live merchant traffic

Base URLs are provided at onboarding. Never mix credentials across environments — a sandbox key won’t work in production and vice versa.

Sandbox

The sandbox is a full functional copy of production with:

  • Same APIs, same request/response shapes
  • Same webhook mechanism
  • Same reports
  • Same dashboard
  • Fake money — no actual settlements or bank transfers
  • Simulated processing behaviour — orders succeed / fail based on test payment identifiers (see below)
Sandbox test data

We provide test payment identifiers that trigger specific outcomes:

Test identifier Behaviour
VPA success@upi Order succeeds immediately
VPA fail@upi Order fails immediately
VPA pending@upi Order stays PENDING for 2 minutes then succeeds
Card 4111111111111111 Card success
Card 4000000000000002 Card decline

Full list on the sandbox dashboard.

Sandbox limits
  • Rate limit: 100 requests / minute per merchant.
  • Data retention: sandbox data purged after 90 days.
  • No SLA — sandbox may have brief downtime for testing new features.
Sandbox webhook

Your sandbox callback URL must be publicly reachable. Common options:

  • ngrok / cloudflare tunnel to expose your local dev server
  • A staging environment with a public URL
Production

Real merchants. Real money. Real chargebacks. Full SLA applies.

Production access

Access is gated by sandbox certification:

  1. Complete sandbox integration.
  2. Run through the certification checklist with our team.
  3. Production credentials issued.
  4. Callback URL + egress IPs whitelisted.
  5. Go live.

Certification typically takes 2–3 days after you tell us you’re ready.

Production limits

Rate limits are tier-dependent based on your commercial agreement. Default: 1000 requests / minute per merchant. Contact us for higher limits.

Data retention: 7 years (regulatory requirement).

Going live

Checklist:

Environment differences to watch for

Places where sandbox and production may differ:

  1. Settlement cadence. Sandbox settles instantly (simulated); production settles per real processing cadence (typically 2–3× daily).
  2. Chargeback frequency. No chargebacks in sandbox by default. Trigger via test identifiers if you want to test chargeback handling.
  3. Reserve release. Sandbox may accelerate the reserve maturity window for testing.
  4. Rate limits. Sandbox is lower to prevent accidental load.
  5. Report file availability. Reports are generated on the same cadence in both environments, but sandbox data may be sparse.

Design your integration to be robust to production timing. Don’t assume settlement lands instantly just because sandbox behaves that way.

Best practice: three-tier setup on your side

Recommended:

  • Local dev — your developers hit sandbox from their machines (via tunnel if needed for webhooks).
  • Staging — your staging environment hits sandbox with a dedicated sandbox key.
  • Production — your production servers hit our production with a dedicated production key.

Never share keys across your own environments.

Companion docs
Getting Started

Fees & GST

When you need this

Before you go live — so you understand what you’ll be charged.

What you’re charged

Every transaction carries a bundled commission shown on your reports as comm (short for commission).

  • comm is inclusive of GST.
  • Commission rates are defined in your commercial agreement per payment mode.
When commission is charged

Pay-ins: deducted from the settled amount when the transaction settles. If a customer pays ₹1000, you receive 1000 − comm − reserve net at settlement.

Payouts: deducted from your transaction balance at the time of payout. If you initiate a ₹1000 payout, 1000 + comm is debited from your balance; the beneficiary receives ₹1000.

Rolling reserve

For pay-ins on some payment modes, a rolling reserve is withheld — typically 1% of the transaction, held for 90 days as chargeback protection.

  • Reserve is your money, temporarily held.
  • Released back to your withdrawable balance on maturity.
  • Applies per transaction — each order’s reserve matures independently.
  • On chargeback, reserve on that order is drawn against first.

See Balances Explained and Reserve Report.

Chargeback penalty

If a chargeback lands, a fixed penalty (per your agreement) applies in addition to the chargeback amount. Chargebacks reduce your balance by chargebackAmount + penalty.

Payout failures

If a payout fails at the bank, the full amount (amount + comm) is credited back to your transaction balance automatically. No manual reconciliation on your side.

Fee estimation vs actuals

Before settlement, comm and reserve are estimates based on your configured rates. After settlement they are actuals.

Typically actuals match estimates within 1–2%.

Companion docs
Concepts

Balances Explained

When you need this

When looking at your balance and trying to understand what each number means. Also when your finance team asks “how much can we actually withdraw right now?”

The four numbers

Your account has four balance numbers. Each answers a different question.

GET /api/v1/balance

Response:

{
  "transactionBalance": 12000.00,
  "reserveBalance":       500.00,
  "outstandingFee":       240.00,
  "withdrawableBalance": 9260.00,
  "currency": "INR",
  "asOf": "2026-07-28T14:30:00+05:30"
}
What each number means
transactionBalance — total entitlement

The money you own or owe on our books. Can be negative if chargebacks exceed prior earnings.

Answers: “What is my net position on this platform?”

reserveBalance — held back

Money withheld as rolling reserve, sitting in a per-order pool. Released back on the reserve maturity date (typically 90 days after the transaction).

Always ≥ 0.

Answers: “How much is temporarily held back on my behalf?”

outstandingFee — what you owe us

Accumulated platform commission + GST from your successful transactions that we haven’t yet recovered. We recover automatically from your subsequent payouts.

Always ≥ 0.

Answers: “How much do I owe the platform right now?”

withdrawableBalance — what you can pay out

The maximum amount you can initiate as a payout right now.

Answers: “How much can I payout at this moment?”

withdrawableBalance may be less than transactionBalance − outstandingFee because part of your money may still be moving through settlement. If you need it out sooner, contact support.

Worked examples
Example 1: Fresh account, one successful order

Order of ₹1000 succeeds. Config: 2% platform fee, 18% GST on that, 1% reserve.

After settlement: - transactionBalance = 1000.00 - reserveBalance = 10.50 - outstandingFee = 23.60 (20 platform_fee + 3.60 gst) - withdrawableBalance = 965.90

Example 2: Chargeback creating debt

A chargeback of ₹1200 lands after having ₹1000 in transactionBalance.

  • transactionBalance = 1000 − 1200 = -200 (negative = you owe)
  • reserveBalance = 0 (chargeback covered from reserve)
  • outstandingFee = 23.60 + 205 = 228.60 (shortfall accrued)
  • withdrawableBalance = 0 (in debt; payouts blocked)

To recover, transact more. Debt resolves via new orders.

Example 3: Reserve maturing after 90 days

Reserve of ₹300 from an order 90 days ago matures.

Before: - reserveBalance = 500 (including the ₹300 about to mature) - withdrawableBalance = 4000

After the release event: - reserveBalance = 200 - withdrawableBalance increases by 300

Invariants worth memorizing
  1. withdrawableBalance ≤ transactionBalance
  2. outstandingFee ≥ 0 — you never owe negative
  3. reserveBalance ≥ 0
  4. transactionBalance can be negative — debt from chargebacks
When numbers can look surprising
  • transactionBalance < 0 — you’re in debt from chargebacks. You cannot initiate payouts until this resolves.
  • withdrawableBalance = 0 but transactionBalance > 0 — likely a large outstandingFee eating into it, or a portion of your balance is still moving through settlement.
Companion docs
Concepts

Callbacks

When you need this

When integrating our platform, so you don’t have to poll our APIs to learn about state changes. We push you a callback every time something material happens.

What a callback is

A callback is an HTTP POST from our servers to your callback_url, delivered whenever a merchant-visible state change happens on one of your entities (orders, payouts, chargebacks, reserve releases).

Callbacks are how you learn about async events: - An order moves from PENDING to SUCCESS - A payout completes or fails - A chargeback lands

Contract summary
Property Value
Method POST
Content-Type application/json
Delivery At-least-once
Ordering Latest-state semantics (via version field)
Retry Yes, up to 24 hours with exponential backoff
Signature HMAC-SHA256 header
Idempotency key version on the entity
Timeout 10 seconds
Expected response HTTP 2xx
Delivery guarantees
  • Zero-loss. Every committed state change is delivered.
  • At-least-once. You may see the same event more than once.
  • Latest state wins. Callbacks always carry the entity’s current state, not the intermediate state that triggered the callback.
  • Version-ordered. The version field increments monotonically per entity. Ignore any callback with version ≤ your last processed version.
Payload shape

Standard envelope:

{
  "eventType": "order.updated",
  "eventId": "evt_a1b2c3d4",
  "eventTimestamp": "2026-07-28T14:30:00+05:30",
  "entityType": "order",
  "entityId": "your-order-001",
  "version": 3,
  "previousVersion": 2,
  "data": {
    // Full current state of the entity
  }
}
  • eventType — one of order.updated, payout.updated, chargeback.landed, reserve.released.
  • eventId — unique per delivery attempt (not per event — retries may have different eventIds).
  • entityId — your clientOrderId / clientPayoutId.
  • version — the entity’s version at the time of this callback.
  • data — full current entity state.

Full payload shape per event type documented in Webhooks API reference.

Idempotency on your side (critical)

Because delivery is at-least-once, you MUST dedupe:

Correct pattern:

receive callback {entityId, version, data}
lookup your record for entityId
if callback.version <= your record's last_processed_version:
    respond 200, no-op
else:
    update your record with data
    set last_processed_version = callback.version
    respond 200

Incorrect pattern (do NOT do this):

  • Blindly overwrite state on every callback — retries will regress state.
  • Use eventId for dedup — that’s per-attempt, not per-event.

Use version for dedup. That’s what it’s for.

Retry behaviour

If your endpoint returns non-2xx or times out:

  • Retry with exponential backoff: 1m, 5m, 15m, 1h, 3h, 6h, 12h, 24h.
  • After 24 hours of failed retries, the callback moves to a “dead letter” state and we alert you.
  • You can query pending callbacks: GET /api/v1/ops/callbacks/pending.
  • You can manually replay a failed callback via the dashboard.
Ordering

Callbacks for the same entity may arrive out of order under retry conditions. version disambiguates — always prefer the higher version.

Callbacks for different entities are unordered relative to each other.

Signature verification

Every callback includes an HMAC-SHA256 signature in the header:

X-Signature-256: sha256=<hex-encoded HMAC of raw request body using your webhook secret>

Verify every callback. Reject any callback where the signature doesn’t match.

Your webhook secret is issued at onboarding, separate from your API key. Rotate the same way — dashboard → Settings → Webhook Secret.

Example verification (Java):

String expected = HmacUtils.hmacSha256Hex(webhookSecret, rawBody);
String provided = request.getHeader("X-Signature-256").substring(7); // strip "sha256="
if (!MessageDigest.isEqual(expected.getBytes(), provided.getBytes())) {
    return response.status(401);
}
Callback URL requirements
  • HTTPS only. No HTTP.
  • Valid TLS certificate. No self-signed.
  • Publicly reachable. No internal-network-only URLs.
  • Responds within 10 seconds. Do your heavy processing async; respond 200 fast.
  • Idempotent. Handles duplicate deliveries safely.
  • Immutable per order — the callback URL you pass at order creation is the one we call for that order.
Different callback URLs per environment

Use different URLs for sandbox and production:

Sandbox:   https://your-staging.example.com/webhooks/payment
Production: https://your-api.example.com/webhooks/payment

Configured on your account per environment.

Debugging tips
  • Log every callback received — raw body + headers. Retention minimum 30 days.
  • Verify signature every time — even in sandbox.
  • Respond 200 quickly — 10-second budget. Do work async.
  • Alert on webhook failures on your side — a spike in 5xx responses from your endpoint should page someone.
  • Use the sandbox webhook test tool (dashboard → Settings → Webhooks → Send Test) to validate your endpoint at any time.
Companion docs
API Reference

API Overview

When you need this

Before writing your first API call. This covers the fundamentals that apply to every endpoint.

Base URL
Sandbox:    <SANDBOX_BASE_URL>
Production: <PRODUCTION_BASE_URL>

All API paths are relative to this base URL.

Versioning

Current version: v1.

Every path is prefixed with /api/v1/. When we release v2, v1 will remain supported for a minimum of 12 months alongside.

Breaking changes only happen at a major version boundary. Additive changes (new optional fields, new endpoints) may land in v1 without notice, so make your client tolerant of unknown fields.

Request headers

Every request requires:

Header Value
X-Client-Id Your merchant id
X-Api-Key Your API key
Content-Type application/json for POST/PUT
Accept application/json

Optional:

Header Purpose
X-Request-Id Your correlation id. We echo it back in the response header.
Idempotency-Key For POSTs; see Idempotency
Response envelope

All JSON responses follow this shape:

Success:

{
  "data": { ... },
  "meta": {
    "requestId": "req_a1b2c3",
    "timestamp": "2026-07-28T14:30:00+05:30"
  }
}

Error:

{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Human-readable error",
    "details": {
      "field": "amount",
      "reason": "Must be positive"
    }
  },
  "meta": {
    "requestId": "req_a1b2c3",
    "timestamp": "2026-07-28T14:30:00+05:30"
  }
}

See Errors for the full error taxonomy.

HTTP status codes
Status Meaning
200 Success
201 Created (POST)
400 Bad request — invalid input
401 Auth failure
403 Forbidden — IP, permissions
404 Not found
409 Conflict — usually idempotency
422 Business validation failure (e.g. insufficient balance)
429 Rate limited
500 Our error
502 / 503 / 504 Service temporarily unavailable — safe to retry

Rule of thumb: 4xx = your input, 5xx = our problem, 429 = slow down, 502/503/504 = retry with backoff.

Pagination

List endpoints use offset-limit pagination:

GET /api/v1/orders?limit=50&offset=0
  • Default limit = 50. Max 200.
  • Response includes meta.pagination:
"pagination": {
  "limit": 50,
  "offset": 0,
  "total": 1234,
  "hasMore": true
}

For large exports, prefer the bulk export endpoints (see Reports) over paginated list calls.

Filtering

Common filters across endpoints:

Parameter Meaning
since ISO-8601 timestamp — inclusive lower bound
until ISO-8601 timestamp — exclusive upper bound
status Comma-separated list of statuses

Endpoint-specific filters documented per endpoint.

Timestamps

Every timestamp we emit is ISO-8601 with timezone offset:

2026-07-28T14:30:00+05:30

Every timestamp you send must be in the same format.

Amounts
  • Decimal, plain numbers, two decimal places
  • No currency symbol, no thousands separator
  • Currency in a separate field (INR in v1)

Example: 500.00, not ₹500 or 500 or 500.0.

Idempotency

POST endpoints support idempotency via: 1. A stable Idempotency-Key header, OR 2. Business idempotency via your clientOrderId / clientPayoutId.

See Idempotency.

Rate limits

Default: 1000 requests / minute per merchant in production, 100 in sandbox. Contact us for higher limits.

Rate-limited responses return HTTP 429 with Retry-After header (seconds).

See Rate Limits.

CORS

Our APIs are not intended for direct browser calls. Do not CORS-enable them for merchant websites. All calls should go from your backend server.

If you need browser-side flows (e.g. customer payment page redirect), use the payment URL we return with the order — it handles browser security correctly.

Available endpoints (v1)
Orders
  • POST /api/v1/orders — create an order
  • GET /api/v1/orders/{clientOrderId} — get one order
  • GET /api/v1/orders — list orders
Payouts
  • POST /api/v1/payouts — create a payout
  • GET /api/v1/payouts/{clientPayoutId} — get one payout
  • GET /api/v1/payouts — list payouts
Balance
  • GET /api/v1/balance — current balance snapshot
Ops
  • GET /api/v1/ops/callbacks/pending — see what’s queued for delivery to your webhook

Full detail per endpoint in the API reference section.

Companion docs
API Reference

Errors

When you need this

Any time an API call returns non-2xx. This is the reference for what our error codes mean and how you should handle each one.

Error envelope
{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Amount 1500 exceeds withdrawable balance 1200",
    "details": {
      "amount": 1500,
      "withdrawableBalance": 1200
    }
  },
  "meta": {
    "requestId": "req_a1b2c3",
    "timestamp": "2026-07-28T14:30:00+05:30"
  }
}

Always match on error.code — never on error.message (message text may change).

Error taxonomy

Codes grouped by HTTP status.

400 — Bad request
Code Meaning What to do
INVALID_REQUEST Payload malformed or missing required fields Check details.field and fix the request
INVALID_AMOUNT Amount ≤ 0 or otherwise invalid Send a positive amount with 2 decimals
INVALID_CURRENCY Currency not supported Only INR in v1
INVALID_PAYMENT_MODE Payment mode not enabled for your account Check enabled modes in dashboard
INVALID_BENEFICIARY Beneficiary details incomplete or invalid Ensure account number + IFSC valid
INVALID_CALLBACK_URL Callback URL not HTTPS or unreachable Fix your endpoint
INVALID_DATE_RANGE since / until invalid Ensure ISO-8601 and since < until
401 — Auth
Code Meaning What to do
UNAUTHORIZED Missing or invalid X-Api-Key Check credentials
CLIENT_ID_MISMATCH X-Client-Id doesn’t match the key Fix X-Client-Id
403 — Forbidden
Code Meaning What to do
IP_NOT_ALLOWED Source IP not on allowlist Add IP via dashboard
KEY_REVOKED This API key was revoked Use an active key
ACCOUNT_DISABLED Your account is disabled Contact support
PERMISSION_DENIED Not authorized for this action Check with your account admin
404 — Not found
Code Meaning What to do
ORDER_NOT_FOUND No order with the given clientOrderId Verify the id
PAYOUT_NOT_FOUND No payout with the given clientPayoutId Verify the id
REPORT_NOT_FOUND Report not yet generated Wait for cadence or contact support
409 — Conflict
Code Meaning What to do
DUPLICATE_ORDER Same clientOrderId used for a different order Use a fresh id, or GET the existing order
DUPLICATE_PAYOUT Same clientPayoutId used for a different payout Same as above
IDEMPOTENCY_KEY_REUSED Idempotency-Key reused with different payload Use a new key or the original payload
422 — Business rule violation
Code Meaning What to do
INSUFFICIENT_BALANCE_CLIENT_DEBT Your transactionBalance is negative — payouts blocked Recover via new orders; contact support if persistent
INSUFFICIENT_WITHDRAWABLE_BALANCE Requested amount > withdrawableBalance Reduce amount or wait for settlement
ORDER_INITIATION_TIMEOUT Order didn’t initiate within timeout Retry the order
PAYOUT_LIMIT_EXCEEDED Payout amount exceeds your per-transaction limit Split into smaller payouts
DAILY_LIMIT_EXCEEDED Cumulative daily volume exceeded Wait for next day or contact for limit increase
INVALID_STATE_TRANSITION Attempted operation not allowed in current state Verify order/payout current status
429 — Rate limit
Code Meaning What to do
RATE_LIMIT_EXCEEDED Too many requests Back off per Retry-After header
500 — Server error
Code Meaning What to do
INTERNAL_ERROR Something went wrong on our side Retry with backoff; contact support with requestId if persistent
502 / 503 / 504 — Service unavailable
Code Meaning What to do
SERVICE_UNAVAILABLE Service temporarily unavailable Retry with backoff
SERVICE_TIMEOUT Service timed out Retry with backoff
PROCESSING_ERROR Uncategorized service error Retry; if persistent, contact support with requestId
Retry guidance
Response Retry? How
2xx No Success
400 / 401 / 403 / 404 / 409 / 422 No Fix the request; retrying won’t help
429 Yes Wait per Retry-After, then retry
500 Yes Exponential backoff: 1s, 2s, 4s, 8s, 16s
502 / 503 / 504 Yes Exponential backoff as above
Timeout Yes, with idempotency Use Idempotency-Key; safe to retry

Idempotency is critical when retrying POSTs. See Idempotency.

Handling INSUFFICIENT_BALANCE_CLIENT_DEBT

If you see this on a payout attempt, your account is in debt from chargebacks. Debt recovery:

  1. Continue creating orders — each successful order increases transactionBalance.
  2. When transactionBalance ≥ 0, payouts unblock.
  3. Contact support if you need manual intervention.

Debt does NOT block order creation — only payouts.

Handling SERVICE_UNAVAILABLE

Transient. Retry with exponential backoff. Our platform absorbs short processing outages by retrying internally; a persistent SERVICE_UNAVAILABLE from us usually means a serious platform issue.

What to log

For every error response, log at minimum:

  • Timestamp
  • Endpoint + method
  • Request payload (redact sensitive fields)
  • Response status + error.code + error.message
  • meta.requestId — always include this when contacting support

When contacting support about an error, always share the requestId. It’s how we find the failed call in our logs.

Companion docs
API Reference

Idempotency

When you need this

Any time you POST to create an order or payout. Because network retries happen, you must ensure the same operation isn’t executed twice.

Two mechanisms

You have two overlapping mechanisms — use one or both.

1. Business idempotency via your reference

Every POST /orders requires a clientOrderId. Every POST /payouts requires a clientPayoutId. These are unique per merchant.

  • First call with a new id: creates the entity, returns 201.
  • Retry with same id + same payload: returns the original entity (200), no side effects.
  • Retry with same id + different payload: returns 409 DUPLICATE_ORDER / DUPLICATE_PAYOUT. Change one of them.

This is sufficient for most cases.

2. HTTP-level idempotency via Idempotency-Key

For extra safety, or when your clientOrderId is generated late, add:

Idempotency-Key: <a UUID you generate>

Rules: - Key is scoped to the endpoint + your merchant. - Same key + same body + same endpoint = same response. - Same key + different body = 409 IDEMPOTENCY_KEY_REUSED. - Keys are retained for 24 hours.

generate clientOrderId = <your reference>
generate idempotencyKey = uuid()   // stored on your side
attempt:
  POST /orders with body containing clientOrderId
                   header: Idempotency-Key: <idempotencyKey>
  if response is 2xx: done
  if response is 5xx or timeout: retry with SAME body and SAME key
  if response is 409: log; the operation succeeded previously
What we consider “the same request”

For business idempotency (clientOrderId / clientPayoutId): - Same id, same amount, same currency, same payment mode, same callback URL — treated as the same request. - Different in any of the above — treated as a conflict (409).

For HTTP-level idempotency (Idempotency-Key): - Byte-exact same body, same endpoint, same key — same request. - Any difference in body — 409.

Safe fields to change on retry

Only these fields don’t affect idempotency: - metadata (JSON blob for your custom fields) - Optional descriptive fields (narration, description)

What is NOT idempotent
  • GET endpoints are naturally idempotent — retry freely.
  • PUT / PATCH / DELETE — no PUT/PATCH/DELETE endpoints exist in v1.
  • Status changes triggered by webhooks / polling — these are server-side; you don’t control them.
Common mistakes
Not sending clientOrderId (or reusing one accidentally)

If you don’t set clientOrderId, we generate one for you — but then you can’t safely retry, because we generate a new one each call. Always set clientOrderId from your side.

If you’re reusing ids accidentally (e.g. UUID generation collision in a distributed system, or a database sequence reset), the second call gets 409. Use fresh unique ids per business event.

Retrying with a new clientOrderId after a timeout

If your first POST timed out, you don’t know if we processed it. Never retry with a new clientOrderId — that creates two orders. Retry with the same id (idempotent), or GET to check existence.

Using timestamps as clientOrderId

If two events happen in the same millisecond, you get collisions. Use UUIDs or database-sequence-backed ids.

Recovery from network failure

Timeout on your POST → you don’t know if the order was created. Options in order of preference:

  1. Retry with same id + same key. Idempotent — safe.
  2. GET /orders/{clientOrderId} — if it exists, use it. If 404, the create didn’t happen; retry.
Rate limit interaction

429 responses are safe to retry with the same idempotency key after the Retry-After interval.

Companion docs
API Reference

Orders

When you need this

To collect payment from your customer. This is the main pay-in integration.

Order lifecycle
CREATED  →  PENDING  →  PROCESSING  →  SUCCESS  → (CHARGEBACK)
                                    →  FAILED    → (REFUND)
  • CREATED — accepted by us, submitted for processing.
  • PENDING — customer has payment URL, awaiting action.
  • PROCESSING — customer has initiated payment, awaiting confirmation.
  • SUCCESS — payment confirmed.
  • FAILED — payment permanently failed.
  • CHARGEBACK — later, customer disputed a previously-successful payment.
  • REFUND — later, funds refunded.

You learn about all transitions via callbacks.


POST /api/v1/orders — Create order
Request
curl -X POST <PLATFORM_BASE_URL>/api/v1/orders \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "clientOrderId": "order-001",
    "amount": 500.00,
    "currency": "INR",
    "paymentMode": "UPI",
    "customerReference": "customer_xyz",
    "callbackUrl": "https://your-api.example.com/webhooks/payment",
    "metadata": {
      "cartId": "cart-123",
      "sourceApp": "mobile-android"
    }
  }'
Request body
Field Type Required Description
clientOrderId string Required Your unique reference for this order. Idempotency key.
amount decimal Required Gross amount to collect from customer
currency string Required INR
paymentMode enum Required UPI | CARD | NETBANKING | IMPS
customerReference string Optional Your customer id — helps for chargeback dispute
callbackUrl string Optional Overrides your account’s default callback URL
metadata object Optional Free-form JSON for your custom fields (max 4KB)
Response — 201 Created
{
  "data": {
    "clientOrderId": "order-001",
    "amount": 500.00,
    "currency": "INR",
    "paymentMode": "UPI",
    "status": "PENDING",
    "paymentUrl": "upi://pay?pa=...&am=500&cu=INR",
    "version": 2,
    "estimatedComm": 24.00,
    "estimatedReserve": 5.00,
    "expectedNet": 471.00,
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T14:30:01+05:30"
  },
  "meta": { ... }
}
  • paymentUrl — UPI intent URL or redirect URL for browser-based payment modes. Show this to your customer.
  • estimatedComm / estimatedReserve — pre-settlement estimates. Actuals populated at settlement.
  • expectedNet — what you’ll receive after settlement.
Error responses
  • 400 INVALID_REQUEST — malformed payload
  • 400 INVALID_AMOUNT — amount ≤ 0
  • 400 INVALID_PAYMENT_MODE — payment mode not enabled
  • 409 DUPLICATE_ORDERclientOrderId reused with different payload

GET /api/v1/orders/{clientOrderId} — Get order
Request
curl -X GET <PLATFORM_BASE_URL>/api/v1/orders/order-001 \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..."
Response — 200 OK
{
  "data": {
    "clientOrderId": "order-001",
    "amount": 500.00,
    "currency": "INR",
    "paymentMode": "UPI",
    "status": "SUCCESS",
    "version": 4,
    "estimatedComm": 24.00,
    "estimatedReserve": 5.00,
    "comm": 24.00,
    "reserve": 5.00,
    "net": 471.00,
    "settledAt": "2026-07-28T18:00:00+05:30",
    "utr": "620108734655",
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T18:00:01+05:30",
    "completedAt": "2026-07-28T14:32:00+05:30"
  },
  "meta": { ... }
}

Fields you’ll see once terminal / settled:

Field Meaning
comm Actual commission (platform_fee + gst + processing_cost) after settlement; equals estimatedComm before settlement
reserve Actual reserve withheld; equals estimatedReserve before settlement
net amount − comm − reserve
settledAt When settlement lands
utr Bank UTR — for your bank reconciliation
completedAt When SUCCESS/FAILED was reached
Error responses
  • 404 ORDER_NOT_FOUND

GET /api/v1/orders — List orders
Request
curl -X GET '<PLATFORM_BASE_URL>/api/v1/orders?since=2026-07-01T00:00:00%2B05:30&until=2026-07-28T23:59:59%2B05:30&status=SUCCESS&limit=100' \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..."
Query parameters
Param Type Description
since ISO-8601 timestamp Inclusive lower bound on createdAt
until ISO-8601 timestamp Exclusive upper bound on createdAt
status comma-separated statuses Filter by status
paymentMode enum Filter by payment mode
limit integer Page size, default 50, max 200
offset integer Page offset, default 0
Response — 200 OK
{
  "data": [
    { "clientOrderId": "order-001", ... },
    { "clientOrderId": "order-002", ... }
  ],
  "meta": {
    "pagination": { "limit": 100, "offset": 0, "total": 245, "hasMore": true },
    ...
  }
}

For large historical exports, prefer the pay-in report — see Pay-in Report.


Order status transitions — what each means
CREATED → PENDING

We’ve accepted the order and submitted it for processing. paymentUrl is available.

PENDING → PROCESSING

Customer has begun payment (e.g. entered UPI PIN, entered card details).

PROCESSING → SUCCESS

Payment confirmed. Money is now owed to you. - transactionBalance credited. - outstandingFee accrued. - Callback fires.

PROCESSING → FAILED

Payment permanently failed. No ledger impact. - Callback fires.

SUCCESS → CHARGEBACK

Customer disputed the payment. Funds clawed back. - transactionBalance debited. - Reserve may be applied first before other balances. - Chargeback penalty added to what you owe. - Callback fires. See Chargeback Report.

SUCCESS → REFUND

Payment refunded to customer. - transactionBalance debited. - Our commission reversed for this transaction. - Callback fires.

After the terminal state

Even after an order is in a terminal state, additional events can happen:

  • Settlement event — updates comm, reserve, settledAt. Does not change status.
  • Reserve release — updates reserveReleasedAt. Does not change status.
  • Chargeback — flips status to CHARGEBACK.
  • Refund — flips status to REFUND.

All emit callbacks.

Best practices
  • Always send clientOrderId — never let us generate it.
  • Verify signature on every callback — see Callbacks.
  • Handle at-least-once delivery — dedupe by version.
  • Don’t ship customer-facing pages until you’ve handled at least SUCCESS and FAILED callbacks.
  • Log utr (UTR) — critical for bank reconciliation later.
Companion docs
API Reference

Payouts

When you need this

To disburse funds from your platform balance to a beneficiary bank account. Standard flows: paying out to customers, suppliers, employees, or transferring to your own bank.

Payout lifecycle
CREATED  →  VALIDATED  →  QUEUED  →  PROCESSING  →  SUCCESS
                                                 →  FAILED
  • CREATED — accepted by our API.
  • VALIDATED — business validations passed (balance, beneficiary).
  • QUEUED — awaiting execution slot.
  • PROCESSING — submitted for processing.
  • SUCCESS — bank confirmed receipt at beneficiary.
  • FAILED — permanently failed. Money is credited back to your balance automatically.

You learn about all transitions via callbacks.


POST /api/v1/payouts — Create payout
Request
curl -X POST <PLATFORM_BASE_URL>/api/v1/payouts \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001" \
  -d '{
    "clientPayoutId": "payout-001",
    "amount": 1000.00,
    "currency": "INR",
    "beneficiaryName": "Ravi Kumar",
    "beneficiaryAccount": "11914600098041",
    "beneficiaryIfsc": "DCBL0000119",
    "beneficiaryEmail": "ravi@example.com",
    "beneficiaryPhone": "+919876543210",
    "narration": "Salary for July 2026",
    "callbackUrl": "https://your-api.example.com/webhooks/payment",
    "metadata": {
      "payrollBatchId": "batch-2026-07"
    }
  }'
Request body
Field Type Required Description
clientPayoutId string Required Your unique reference — idempotency key
amount decimal Required Amount to send to beneficiary (net)
currency string Required INR
beneficiaryName string Required Name on the beneficiary account
beneficiaryAccount string Required Bank account number
beneficiaryIfsc string Required IFSC code
beneficiaryEmail string Optional For beneficiary notification
beneficiaryPhone string Optional For beneficiary notification
narration string Optional Max 40 chars — shows on beneficiary bank statement
paymentMode enum Optional Force a specific mode: IMPS | NEFT | RTGS | UPI. Default: platform selects best mode.
callbackUrl string Optional Overrides default
metadata object Optional Free-form JSON (max 4KB)
Response — 201 Created
{
  "data": {
    "clientPayoutId": "payout-001",
    "amount": 1000.00,
    "currency": "INR",
    "status": "VALIDATED",
    "version": 2,
    "estimatedComm": 118.00,
    "totalDebit": 1118.00,
    "beneficiary": {
      "name": "Ravi Kumar",
      "accountMasked": "***********8041",
      "ifsc": "DCBL0000119"
    },
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T14:30:01+05:30"
  },
  "meta": { ... }
}
  • estimatedComm — pre-execution estimate. Actual set at SUCCESS.
  • totalDebit = amount + estimatedComm — what leaves your balance.
  • beneficiary.accountMasked — last 4 digits visible only.
Error responses
  • 400 INVALID_REQUEST — malformed
  • 400 INVALID_AMOUNT — amount ≤ 0
  • 400 INVALID_BENEFICIARY — account / IFSC invalid
  • 422 INSUFFICIENT_BALANCE_CLIENT_DEBT — your balance is negative
  • 422 INSUFFICIENT_WITHDRAWABLE_BALANCE — insufficient funds
  • 422 PAYOUT_LIMIT_EXCEEDED — over your per-txn limit
  • 422 DAILY_LIMIT_EXCEEDED — over your daily volume limit
  • 409 DUPLICATE_PAYOUTclientPayoutId reused with different payload
Validation gates

Payouts are validated in this order (first failure stops):

  1. Amount > 0
  2. Your transactionBalance ≥ 0 (not in debt)
  3. amount + estimatedComm ≤ withdrawableBalance
  4. Beneficiary details valid (regex + IFSC lookup)
  5. Payout amount within per-transaction limit
  6. Cumulative daily volume within limit

GET /api/v1/payouts/{clientPayoutId} — Get payout
Request
curl -X GET <PLATFORM_BASE_URL>/api/v1/payouts/payout-001 \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..."
Response — 200 OK
{
  "data": {
    "clientPayoutId": "payout-001",
    "amount": 1000.00,
    "currency": "INR",
    "status": "SUCCESS",
    "version": 5,
    "comm": 115.50,
    "totalDebit": 1115.50,
    "beneficiary": {
      "name": "Ravi Kumar",
      "accountMasked": "***********8041",
      "ifsc": "DCBL0000119"
    },
    "utr": "620113416670",
    "narration": "Salary for July 2026",
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T14:35:22+05:30",
    "completedAt": "2026-07-28T14:35:20+05:30"
  }
}
  • utr — bank rail reference. Beneficiary’s bank statement will show this. Your bank reconciliation will match against it.
  • errorCode and errorMessage present only for FAILED status.
Error responses
  • 404 PAYOUT_NOT_FOUND

GET /api/v1/payouts — List payouts
Request
curl -X GET '<PLATFORM_BASE_URL>/api/v1/payouts?since=2026-07-01T00:00:00%2B05:30&status=SUCCESS,FAILED&limit=100' \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..."
Query parameters
Param Type Description
since ISO-8601 Inclusive lower bound on createdAt
until ISO-8601 Exclusive upper bound
status comma-separated Filter
limit integer Default 50, max 200
offset integer Default 0

For large exports use the payout report — see Payout Report.


Payout status transitions
CREATED → VALIDATED

Balance + beneficiary checks passed. Callback fires.

VALIDATED → QUEUED

Accepted for execution. Callback fires.

QUEUED → PROCESSING

Submitted for processing. Callback fires.

PROCESSING → SUCCESS

Bank confirmed receipt. - transactionBalance debited by amount + comm. - outstandingFee reduced (for fee-recovery payouts). - Callback fires with utr.

PROCESSING → FAILED

Payment permanently failed at bank. - Automatic credit-back: transactionBalance credited by amount + comm. You are made whole. No action needed. - Callback fires with errorCode + errorMessage.

Common errorCode values (examples)

This is a non-exhaustive list. Always handle unknown errorCode gracefully.

errorCode Meaning Retry with same payload?
BENEFICIARY_INVALID_ACCOUNT Bank rejected — invalid account No — fix beneficiary details
BENEFICIARY_INVALID_IFSC Bank rejected — invalid IFSC No
BENEFICIARY_ACCOUNT_CLOSED Account no longer active No
BENEFICIARY_NAME_MISMATCH Name doesn’t match account No
NPCI_LIMIT_EXCEEDED Bank rail limit hit Yes — try later, or use different mode
BANK_UNAVAILABLE Beneficiary bank down Yes — retry after 15 min
UNKNOWN Uncategorized bank failure Investigate + contact support
Payment mode selection

If you don’t specify paymentMode, we pick:

  • IMPS for amounts ≤ ₹5 lakh (24×7 real-time)
  • RTGS for amounts > ₹2 lakh during banking hours (fastest for large)
  • NEFT as fallback outside banking hours for large amounts
  • UPI if beneficiary VPA is present (not yet supported in v1)

You can force a mode with the paymentMode field.

Best practices
  • Always set clientPayoutId — never let us generate it.
  • Handle FAILED gracefully — auto credit-back means no action on the money, but your business logic (e.g. retry payroll) needs the notification.
  • Log the utr — beneficiary support requests will reference it.
  • Verify beneficiary name against bank — set beneficiaryName accurately; mismatches at some banks cause rejection.
  • Batch payouts if you have many — call the API in a loop with unique clientPayoutId per payout. No bulk endpoint in v1.
  • Monitor outstandingFee — if it’s high, our automatic recovery may take some payout amount as fee-recovery.
Fee recovery payouts (internal)

Our platform may automatically create fee-recovery payouts to recover accumulated outstandingFee from your balance. These are:

  • Not initiated by you (you’ll see them in reports though).
  • Beneficiary is us, not your beneficiary.
  • Marked with a distinct type in the payout report.
  • Do NOT affect your ability to create your own payouts.
Companion docs
API Reference

Balance

When you need this

Any time you need a real-time view of your account balance.

GET /api/v1/balance
Request
curl -X GET <PLATFORM_BASE_URL>/api/v1/balance \
  -H "X-Client-Id: merchant_abc123" \
  -H "X-Api-Key: sk_live_..."
Response — 200 OK
{
  "data": {
    "currency": "INR",
    "transactionBalance": 12000.00,
    "reserveBalance": 500.00,
    "outstandingFee": 240.00,
    "withdrawableBalance": 9260.00,
    "asOf": "2026-07-28T14:30:00+05:30"
  },
  "meta": {
    "requestId": "req_a1b2c3",
    "timestamp": "2026-07-28T14:30:00+05:30"
  }
}
Field reference
Field Meaning
transactionBalance Your net position. Can be negative.
reserveBalance Rolling reserve held back. Releases per order on maturity.
outstandingFee Platform commission + GST you owe us. Recovered from future payouts.
withdrawableBalance Max amount you can payout right now.
asOf Timestamp of this snapshot.

Full explanation of what each number means: Balances Explained.

  • Minimum interval: 10 seconds.
  • For real-time state changes, use callbacks.
Companion docs
API Reference

Webhooks

When you need this

When implementing the endpoint on your side that receives our callbacks. Complementary to Callbacks concept which covers the principles; this doc gives the exact payload for each event type.

Delivery contract
  • Method: POST
  • Content-Type: application/json
  • Signature header: X-Signature-256: sha256=<hmac>
  • Delivery timeout: 10 seconds
  • Expected response: any HTTP 2xx
  • Retry policy: exponential backoff up to 24 hours
Common envelope

Every callback has this shape:

{
  "eventType": "order.updated",
  "eventId": "evt_a1b2c3d4",
  "eventTimestamp": "2026-07-28T14:30:00+05:30",
  "entityType": "order",
  "entityId": "your-order-001",
  "version": 3,
  "previousVersion": 2,
  "data": {
    // Full current entity state  shape depends on eventType
  }
}
  • eventType — see enumeration below
  • eventId — unique per delivery attempt
  • entityTypeorder | payout
  • entityId — your clientOrderId / clientPayoutId
  • version — monotonic per entity; dedup key
  • previousVersion — previous version at your last known state (informational)
  • data — full state at version
Event types
eventType When it fires
order.updated Any transition on an order — CREATED, PENDING, PROCESSING, SUCCESS, FAILED, CHARGEBACK, REFUND, or settlement / reserve release events
payout.updated Any transition on a payout

Only two eventType values in v1 — the specific state change is reflected in data.status and data fields.

Payload — order.updated
{
  "eventType": "order.updated",
  "eventId": "evt_9f8e7d6c",
  "eventTimestamp": "2026-07-28T14:32:00+05:30",
  "entityType": "order",
  "entityId": "order-001",
  "version": 3,
  "previousVersion": 2,
  "data": {
    "clientOrderId": "order-001",
    "amount": 500.00,
    "currency": "INR",
    "paymentMode": "UPI",
    "status": "SUCCESS",
    "comm": 24.00,
    "reserve": 5.00,
    "net": 471.00,
    "utr": "620108734655",
    "settledAt": null,
    "customerReference": "customer_xyz",
    "metadata": { "cartId": "cart-123" },
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T14:32:00+05:30",
    "completedAt": "2026-07-28T14:32:00+05:30"
  }
}
What triggers version bumps on orders
Trigger New status New version
Order created + submitted for processing PENDING 2
Customer initiated payment PROCESSING 3
Payment success SUCCESS 4
Payment failure FAILED 4
Settlement lands (updates comm, reserve, settledAt) unchanged 5
Chargeback lands CHARGEBACK next
Refund lands REFUND next

Even when status doesn’t change, the version bumps if anything merchant-visible changed — that’s when settlement data or reserve release fires an update.

Payload — payout.updated
{
  "eventType": "payout.updated",
  "eventId": "evt_1a2b3c4d",
  "eventTimestamp": "2026-07-28T14:35:20+05:30",
  "entityType": "payout",
  "entityId": "payout-001",
  "version": 5,
  "previousVersion": 4,
  "data": {
    "clientPayoutId": "payout-001",
    "amount": 1000.00,
    "currency": "INR",
    "status": "SUCCESS",
    "comm": 115.50,
    "totalDebit": 1115.50,
    "beneficiary": {
      "name": "Ravi Kumar",
      "accountMasked": "***********8041",
      "ifsc": "DCBL0000119"
    },
    "utr": "620113416670",
    "narration": "Salary for July 2026",
    "metadata": { "payrollBatchId": "batch-2026-07" },
    "createdAt": "2026-07-28T14:30:00+05:30",
    "updatedAt": "2026-07-28T14:35:20+05:30",
    "completedAt": "2026-07-28T14:35:20+05:30"
  }
}

For FAILED:

"data": {
  "status": "FAILED",
  "errorCode": "BENEFICIARY_INVALID_ACCOUNT",
  "errorMessage": "Account number does not exist at destination bank",
  ...
}
Signature verification

Header:

X-Signature-256: sha256=<hex-encoded HMAC-SHA256 of raw request body using your webhook secret>

Your webhook secret is separate from your API key. Issued at onboarding, rotatable via the dashboard.

Verification example (Java, Spring)
@PostMapping("/webhooks/payment")
public ResponseEntity<?> handle(
        @RequestBody(required = false) String body,
        @RequestHeader("X-Signature-256") String sigHeader) {

    String provided = sigHeader.substring("sha256=".length());
    String computed = HmacUtils.hmacSha256Hex(webhookSecret, body);

    if (!MessageDigest.isEqual(
            provided.getBytes(StandardCharsets.UTF_8),
            computed.getBytes(StandardCharsets.UTF_8))) {
        return ResponseEntity.status(401).build();
    }

    // Body is verified — parse and process
    ...
    return ResponseEntity.ok().build();
}
Verification example (Node.js)
const crypto = require("crypto");

function verify(rawBody, signatureHeader, secret) {
  const provided = signatureHeader.replace(/^sha256=/, "");
  const computed = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(provided, "utf8"),
    Buffer.from(computed, "utf8")
  );
}

Use timing-safe comparison (not === / .equals()) to prevent timing attacks.

Order of message-processing steps on your side
1. Receive POST
2. Verify signature — reject 401 if mismatch
3. Parse JSON
4. Look up your record by entityId
5. If callback.version <= your record's last_processed_version:
     respond 200, no-op
   Else:
     update your record with data
     set last_processed_version = callback.version
     respond 200
6. Enqueue any async work you want to do (send email, update
   downstream systems, etc.) — do NOT do it synchronously if it
   takes >5 seconds
Rate at which callbacks arrive

Under normal load: nearly real-time (< 5 seconds after the state change). Under heavy load: may lag by up to 60 seconds. Late corrections (chargebacks discovered days later) can fire any time.

Design for latency. Don’t rely on a callback arriving within milliseconds.

Testing

Sandbox webhook test: dashboard → Settings → Webhooks → Send Test. Sends a synthetic order.updated callback with test data so you can validate your endpoint end-to-end.

Replay a callback: dashboard → any order → Callbacks tab → Replay. Re-sends the latest callback.

Retention & operations
  • Pending callbacks are queryable via GET /api/v1/ops/callbacks/pending.
  • Callback delivery history is retained for 90 days.
  • Failed callbacks past 24h are marked as dead-letter and surfaced in your dashboard for manual intervention.
Companion docs
Reports

Reports Overview

When you need this

To understand what reports you’ll receive.

What we give you

Six reports, all merchant-facing.

Report Cadence Use case
Pay-in report Daily snapshot Every transaction you collected
Payout report Daily snapshot Every payout you initiated
Settlement report Daily Daily roll-up of what settled
Chargeback report On-demand All chargebacks
Reserve report On-demand Reserve deposits, releases, outstanding balance
How you access them

Dashboard: available under Reports → Downloads in your merchant dashboard. Filter by date range, download as CSV.

Scheduled delivery to your storage: on request, we push all files to a destination of your choice on a daily schedule:

  • SFTP — we push to your SFTP server
  • Cloud storage — Amazon S3 or Google Cloud Storage bucket
  • Email — files sent as attachments

Setup is handled by our team during onboarding.

Common formats
  • CSV — standard format for spreadsheet import.
  • Timestamps in ISO-8601 with timezone offset.
  • Amounts as plain decimals.
  • Stable column names — never change without notice.
Reconciliation-friendly design

All reports carry:

  • Your clientOrderId / clientPayoutId
  • utr — bank rail reference for reconciliation with your bank
  • Timestamps in ISO-8601 with timezone offset
Report retention

Available for 7 years (regulatory requirement).

Alerts

Configure alerts in the dashboard to be notified when:

  • A chargeback lands
  • A payout fails
  • Your outstandingFee crosses a threshold
  • Your reserve balance crosses a threshold
Companion docs
Reports

Pay-in Report

When you need this

To see every transaction you collected, with commission and reserve per row. Your primary reconciliation input.

Columns
Column Type Meaning
clientOrderId string Your reference
amount decimal Gross amount customer paid
status enum Order status
comm decimal Bundled commission
reserve decimal Reserve withheld
net decimal amount − comm − reserve
transactionDate ISO-8601 When the transaction was created
updatedAt ISO-8601 Last modification
utr string Bank UTR — for terminal rows
Sample rows
clientOrderId,amount,status,comm,reserve,net,transactionDate,updatedAt,utr
order-001,500.00,SUCCESS,24.00,5.00,471.00,2026-07-28T14:30:00+05:30,2026-07-28T18:00:00+05:30,620108734655
order-002,1000.00,SUCCESS,48.00,10.00,942.00,2026-07-28T15:00:00+05:30,2026-07-28T18:00:00+05:30,687512088068
order-003,300.00,FAILED,0,0,0,2026-07-28T15:30:00+05:30,2026-07-28T15:31:00+05:30,
order-004,750.00,PROCESSING,36.00,7.50,706.50,2026-07-28T15:45:00+05:30,2026-07-28T15:45:00+05:30,
Notes
  • Row updates in place as the order progresses (PENDING → PROCESSING → SUCCESS/FAILED).
  • comm and reserve are estimates until settlement; actuals after.
  • Only one row per clientOrderId.
Companion docs
Reports

Payout Report

When you need this

To see every payout you initiated, with commission and UTR.

Columns
Column Type Meaning
clientPayoutId string Your reference
amount decimal Amount sent to beneficiary
status enum Payout status
comm decimal Bundled commission
totalDebit decimal amount + comm — total taken from your balance
beneficiaryAccountMasked string Last 4 digits
beneficiaryIfsc string IFSC
payoutDate ISO-8601 When you initiated
completedAt ISO-8601 Terminal timestamp
utr string Bank rail reference — for successful payouts
Sample rows
clientPayoutId,amount,status,comm,totalDebit,beneficiaryAccountMasked,beneficiaryIfsc,payoutDate,completedAt,utr
payout-001,1000.00,SUCCESS,115.50,1115.50,***********8041,DCBL0000119,2026-07-28T14:30:00+05:30,2026-07-28T14:35:20+05:30,620113416670
FAILED payouts

When a payout FAILS, we automatically credit back amount + comm to your transactionBalance. No manual reconciliation on your side.

For failure details on a specific payout, use GET /payouts/{id}.

Fee-recovery payouts

Some payouts are initiated by us to recover accumulated outstandingFee from your balance. These: - Appear in your payout report - Do NOT affect your ability to create your own payouts

Companion docs
Reports

Settlement Report

When you need this

For daily cash-flow view of pay-in settlements.

Columns

One row per day.

Column Type Meaning
settlementDate date Day of settlement
txnCount integer Orders settled that day
grossAmount decimal Sum of amounts
totalComm decimal Sum of commissions
reserveDeposited decimal New reserves that day
netSettled decimal gross − comm − reserveDeposited
Sample rows
settlementDate,txnCount,grossAmount,totalComm,reserveDeposited,netSettled
2026-07-01,45,22500.00,1080.00,225.00,21195.00
2026-07-02,52,26000.00,1248.00,260.00,24492.00
Companion docs
Reports

Chargeback Report

When you need this

To see all chargebacks against your account.

What a chargeback is

A chargeback is a customer-initiated dispute against a previously-successful transaction. When it lands:

  • The chargeback amount is reversed from your balance.
  • A fixed chargeback penalty applies per your agreement.
  • Reserve on the affected order is drawn against first.
Columns
Column Type Meaning
clientOrderId string Order that was charged back
chargebackAmount decimal Amount reversed
chargebackPenalty decimal Fixed penalty per your agreement
chargebackRaisedAt ISO-8601 When the chargeback landed
Sample rows
clientOrderId,chargebackAmount,chargebackPenalty,chargebackRaisedAt
o567233582,300.00,500.00,2026-07-27T15:36:37+05:30
o671763313,400.00,500.00,2026-07-28T13:12:12+05:30
Real-time alerts

We email your ops contact whenever a chargeback lands.

You’ll also see the chargeback via a callback on the affected order — order.updated with status = CHARGEBACK.

Chargeback penalty policy
  • Fixed amount per chargeback, per your commercial agreement.
  • Non-refundable — even if you subsequently win a dispute externally, the platform-side penalty stays.
Companion docs
Reports

Reserve Report

When you need this

For cash-flow view of your reserve pool: how much is currently held and released over time.

Columns

One row per day.

Column Type Meaning
date date Day
deposited decimal New reserves withheld that day
released decimal Reserves released back that day
outstandingEOD decimal Total outstanding reserve at end of day
Sample rows
date,deposited,released,outstandingEOD
2026-07-01,225.00,50.00,3400.00
2026-07-02,260.00,75.00,3585.00
When reserves matter more

If your business has: - Recurring / subscription payments — reserve on each recurrence ties up cash. - High-value transactions — the reserve % adds up.

Reserve is your money
  • Not our fee. It’s your money held.
  • Not permanent loss. Only chargebacks consume it — otherwise it releases fully.
  • Applies per transaction. Each order’s reserve matures independently (typically 90 days).
Companion docs
Support

Contact & Support

When you need this

Any time you’re stuck, need clarification, want to raise an issue, or need help investigating a specific transaction.

Support channels
Channel Use for Response time
Email <contact@paysooper.com> < 4 hours
Dashboard support portal Dashboard → Support → New Case < 4 hours
Emergency phone <+91-XXXX-XXXXXX> 24×7, for Sev 1 incidents
Slack (enterprise plans) Dedicated channel provisioned < 1 hour
What to include when raising a case

For faster resolution, include:

  1. Your merchant id (X-Client-Id)
  2. Environment — sandbox or production
  3. clientOrderId / clientPayoutId if relevant
  4. meta.requestId from the API response if applicable
  5. Timestamps (ISO-8601) of when the issue occurred
  6. What you expected vs what happened
  7. Screenshots if UI-related

The requestId is the single most helpful thing — it lets us find the exact API call in our logs.

Severity levels
Severity Definition Response SLA
Sev 1 Production down for you — payments cannot be processed < 30 min
Sev 2 Significant impairment — one payment mode down, callbacks not firing, reports blocked < 2 hours
Sev 3 Isolated issue — one merchant’s order stuck, one payout failed Same business day
Sev 4 Informational — questions, documentation clarifications < 2 business days

Sev 1 requires phone escalation. Email / portal for Sev 2–4.

Common questions we can answer immediately

Before contacting, check if your question is already answered:

  • “Why is my order stuck in PROCESSING?” — Processing status check can take up to 3 days for some payment modes. See order lifecycle in Orders. If > 3 days, contact us.
  • “Why did my payout fail?” — Check errorCode on the payout row. See Payouts errors.
  • “Why is my withdrawableBalance lower than my transactionBalance?” — See Balances Explained.
  • “When will my reserve release?” — See Reserve Report.
  • “Why didn’t my callback fire?” — Check GET /api/v1/ops/callbacks/pending. See Callbacks.
Reporting a bug in documentation

If any of these docs are unclear, wrong, or missing something:

  • Email <contact@paysooper.com> with the URL/path of the doc and what’s off.
  • Or raise a support case tagged as documentation.

We treat doc gaps as bugs.

Feature requests

If we’re missing a capability you need:

  • Email <contact@paysooper.com> with:
    • What you’re trying to do
    • Current workaround (if any)
    • Business impact
  • We review roadmap monthly and reply within 5 business days.
Security disclosure

If you’ve found a security issue, please do not post on public support channels.

  • Email <contact@paysooper.com> — PGP key at that domain’s .well-known path.
  • We acknowledge within 24 hours and respond within 5 business days.
Escalation

If you’re not getting response within SLA:

  1. Reply on the same case with ESCALATE in the subject.
  2. If still stuck, email <contact@paysooper.com>.
  3. If truly urgent and business-critical, call the emergency phone.
Change log

Platform changes are documented in your dashboard.

Feedback

Even outside a specific issue — if something in the platform or docs is friction for you, we want to hear about it. <contact@paysooper.com>.