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 surface —
POST /ordersto collect,POST /payoutsto 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
- Set up your account — sign the agreement, get onboarded.
- Get API credentials — API key + IP allowlist.
- Integrate in sandbox — build against test credentials.
- 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.
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
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:
- Create a new key in the dashboard.
- Deploy it to your servers alongside the old key.
- Verify traffic is flowing on the new key (dashboard shows per-key usage).
- 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
- Never commit API keys to source control. Use environment variables, secret managers, or vault services.
- Never expose API keys to browsers or mobile apps. All calls should go from your backend server.
- Rotate keys on a schedule. Every 90 days minimum.
- Rotate immediately if a key may have been exposed (accidentally logged, leaked in a screenshot, employee left the company).
- Use different keys per environment. Never reuse a production key in staging or vice versa.
- Restrict IP allowlist to the specific servers that need access. No
0.0.0.0/0. - Monitor key usage. Dashboard shows per-key request rate; a sudden spike or drop is worth investigating.
Error responses related to auth
| 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
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:
- Complete sandbox integration.
- Run through the certification checklist with our team.
- Production credentials issued.
- Callback URL + egress IPs whitelisted.
- 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:
- Settlement cadence. Sandbox settles instantly (simulated); production settles per real processing cadence (typically 2–3× daily).
- Chargeback frequency. No chargebacks in sandbox by default. Trigger via test identifiers if you want to test chargeback handling.
- Reserve release. Sandbox may accelerate the reserve maturity window for testing.
- Rate limits. Sandbox is lower to prevent accidental load.
- 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
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).
commis 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
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
withdrawableBalance ≤ transactionBalanceoutstandingFee ≥ 0— you never owe negativereserveBalance ≥ 0transactionBalancecan 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 = 0buttransactionBalance > 0— likely a largeoutstandingFeeeating into it, or a portion of your balance is still moving through settlement.
Companion docs
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
versionfield increments monotonically per entity. Ignore any callback withversion ≤ 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 oforder.updated,payout.updated,chargeback.landed,reserve.released.eventId— unique per delivery attempt (not per event — retries may have different eventIds).entityId— yourclientOrderId/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 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 (
INRin 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 orderGET /api/v1/orders/{clientOrderId}— get one orderGET /api/v1/orders— list orders
Payouts
POST /api/v1/payouts— create a payoutGET /api/v1/payouts/{clientPayoutId}— get one payoutGET /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
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:
- Continue creating orders — each successful order increases
transactionBalance. - When
transactionBalance ≥ 0, payouts unblock. - 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
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.
Recommended pattern
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:
- Retry with same id + same key. Idempotent — safe.
- 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
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 payload400 INVALID_AMOUNT— amount ≤ 0400 INVALID_PAYMENT_MODE— payment mode not enabled409 DUPLICATE_ORDER—clientOrderIdreused 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 changestatus. - Reserve release — updates
reserveReleasedAt. Does not changestatus. - Chargeback — flips
statustoCHARGEBACK. - Refund — flips
statustoREFUND.
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
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— malformed400 INVALID_AMOUNT— amount ≤ 0400 INVALID_BENEFICIARY— account / IFSC invalid422 INSUFFICIENT_BALANCE_CLIENT_DEBT— your balance is negative422 INSUFFICIENT_WITHDRAWABLE_BALANCE— insufficient funds422 PAYOUT_LIMIT_EXCEEDED— over your per-txn limit422 DAILY_LIMIT_EXCEEDED— over your daily volume limit409 DUPLICATE_PAYOUT—clientPayoutIdreused with different payload
Validation gates
Payouts are validated in this order (first failure stops):
- Amount > 0
- Your
transactionBalance≥ 0 (not in debt) amount + estimatedComm ≤ withdrawableBalance- Beneficiary details valid (regex + IFSC lookup)
- Payout amount within per-transaction limit
- 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.errorCodeanderrorMessagepresent only forFAILEDstatus.
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
beneficiaryNameaccurately; mismatches at some banks cause rejection. - Batch payouts if you have many — call the API in a loop with unique
clientPayoutIdper 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
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.
Recommended polling cadence
- Minimum interval: 10 seconds.
- For real-time state changes, use callbacks.
Companion docs
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 beloweventId— unique per delivery attemptentityType—order|payoutentityId— yourclientOrderId/clientPayoutIdversion— monotonic per entity; dedup keypreviousVersion— previous version at your last known state (informational)data— full state atversion
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 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
outstandingFeecrosses a threshold - Your reserve balance crosses a threshold
Companion docs
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).
commandreserveare estimates until settlement; actuals after.- Only one row per
clientOrderId.
Companion docs
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
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
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
- Reserve Report
- Pay-in Report — the affected orders also show up here with
status = CHARGEBACK - Balances Explained
- Fees & GST
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
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 |
|---|---|---|
<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:
- Your merchant id (
X-Client-Id) - Environment — sandbox or production
clientOrderId/clientPayoutIdif relevantmeta.requestIdfrom the API response if applicable- Timestamps (ISO-8601) of when the issue occurred
- What you expected vs what happened
- 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
errorCodeon the payout row. See Payouts errors. - “Why is my
withdrawableBalancelower than mytransactionBalance?” — 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-knownpath. - We acknowledge within 24 hours and respond within 5 business days.
Escalation
If you’re not getting response within SLA:
- Reply on the same case with
ESCALATEin the subject. - If still stuck, email
<contact@paysooper.com>. - 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>.