Paygit API reference
Dashboard

Basics

Collect a payment

1. Create the payment from your server

curl -u key_test_xxx:sk_test_xxx -X POST https://YOUR_HOST/v1/payments \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: order-1001-attempt-1' \
  -d '{
    "order_id": "ORD-1001",
    "amount": 49900,
    "description": "Blue cotton shirt",
    "customer": {"name": "Asha", "email": "[email protected]", "phone": "9876543210"},
    "redirect_url": "https://yourstore.in/order/ORD-1001/done"
  }'
FieldNotes
order_idRequired. Your unique reference, 1-64 chars. Reusing one returns 409 duplicate_order_id.
amountRequired. Paise, minimum 100.
flowintent (default: QR + UPI app link) or collect (needs payer_vpa).
redirect_urlWhere the customer returns after paying. We append payment_id, order_id, status.
expires_inSeconds until the payment expires. 60-86400, default 900.
notesUp to 15 string key/values, returned on the payment and in webhooks.

2. Send the customer to checkout_url

The response includes checkout_url (our hosted page with QR and UPI app button) and upi_uri if you want to render your own QR.

3. Confirm on your server

Never trust the redirect alone. Mark the order paid only when you receive a verified payment.success webhook, or when GET /v1/payments/{id} returns "status": "success" with the amount you expect.

Statuses: pending → success | failed | expired.

GET  /v1/payments/{id}
GET  /v1/payments?order_id=ORD-1001&status=success&limit=25&offset=0

Refunds

POST /v1/payments/{id}/refunds   {"amount": 10000, "reason": "Size issue"}   # omit amount for a full refund
GET  /v1/payments/{id}/refunds
GET  /v1/refunds/{refund_id}

Refunds start as processing and finish as success or failed (webhook: refund.success / refund.failed). The refund is debited from your pending balance if the payment is unsettled, otherwise from your available balance. Fees on the original payment are not returned.

Payouts

curl -u key_test_xxx:sk_test_xxx -X POST https://YOUR_HOST/v1/payouts \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: payout-7781' \
  -d '{
    "reference_id": "VENDOR-7781",
    "amount": 250000,
    "beneficiary": {"name": "Sharma Traders", "vpa": "sharma.traders@okhdfc"},
    "purpose": "vendor payment"
  }'
GET  /v1/payouts/{id}
GET  /v1/payouts?reference_id=VENDOR-7781
POST /v1/payouts/bulk      {"payouts": [ {...}, {...} ]}     # up to 500, each row accepted or rejected on its own
POST /v1/vpa/validate      {"vpa": "name@bank"}
GET  /v1/balance
GET  /v1/settlements

Webhooks

Add an HTTPS endpoint in Dashboard → Developers. We POST JSON like:

{
  "id": "evt_…", "type": "payment.success", "mode": "test", "created_at": "2026-11-13T10:00:00.000Z",
  "data": { "id": "pay_…", "object": "payment", "order_id": "ORD-1001", "amount": 49900, "status": "success", "utr": "431298765012", … }
}

Events: payment.success, payment.failed, payment.expired, refund.success, refund.failed, payout.success, payout.failed, payout.reversed, settlement.created, settlement.paid.

Verify the signature

Header X-Paygit-Signature: t=<unix seconds>,v1=<hex> where v1 = HMAC_SHA256(secret, t + "." + raw_body). Compute it over the raw request bytes and reject timestamps older than 5 minutes.

// Node.js (Express): app.post('/webhooks/paygit', express.raw({type: 'application/json'}), handler)
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const want = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  return want.length === v1.length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}

Testing with the simulator

Use this valueWhat happens
Checkout page buttonsApprove or decline a test payment by hand.
payer_vpa: success@simPayment succeeds automatically after a couple of seconds.
payer_vpa: fail@simPayment is declined automatically.
Payout to fail@sim or an account ending 0000Payout fails and the hold is released.
Payout to pending@simPayout stays processing until resolved in the operator's simulator page.
Any other beneficiaryPayout succeeds.

Common error codes

CodeMeaning
unauthorizedMissing or wrong API key.
validation_errorA field is missing or malformed; see details.
duplicate_order_id / duplicate_reference_idThat reference already exists; details has the existing id.
insufficient_balanceAvailable balance does not cover the payout plus fee, or the refund.
amount_limit_exceeded, daily_limit_exceeded, rail_limit_exceeded, velocity_limit_exceededA risk limit was hit.
blockedThe payer or beneficiary is on the blocklist.
merchant_not_active, live_mode_unavailableLive mode is not available for this account yet.
bank_unavailableThe bank could not be reached. Retry the request.
rate_limitedMore than 1200 requests a minute on one key.