Quickstart

Take a first payment in the sandbox in about ten minutes. You will create a payment from your server, send the customer to the hosted checkout, and confirm the result with a webhook. Nothing here needs a bank account; that comes when you go live.

Take a test payment

  1. Get test keys from the dashboard. Secret keys start with sk_test_, publishable keys with pk_test_. Live keys use sk_live_ and only work from allow-listed IPs.
  2. Install an SDK, or call the REST API directly with basic auth (the key is the username, the password is empty).
  3. Create a payment from your server with the amount in paise. The response carries a url.
  4. Send the customer to that URL, or open it in the SDK sheet on mobile. In the sandbox, UPI ID success@paydash approves and failure@paydash declines; card 4111 1111 1111 1111 with any future expiry approves.
  5. Handle payment.captured on your webhook endpoint and fulfil the order. Never trust the callback URL alone: the customer can close the tab.

Authentication

Every request is authenticated with your secret key over HTTPS. Pin the API version with a Paydash-Version header; without it, requests use the version your account was created on.

Fetch a paymentGET
curl https://api.paydash.in/v1/payments/pay_Mq7Yk2Lw9Xa3 \  -u sk_test_3QwK9xLm2Vb8: \  -H "Paydash-Version: 2026-08-01"

Create a payment

POST /v1/payments idempotent with Idempotency-Key

Creates a payment and a checkout link for it. The payment moves through created, authorised, captured and settled; each transition is a webhook. Send the same Idempotency-Key on a retry and you get the original payment back instead of a second one.

parametertypedescription
amount *integerAmount in paise. ₹499.00 is 49900. Minimum 100.
currency *stringINR, or USD, EUR, GBP, AED, SGD for international cards. Settlement is always in INR.
methodsstring[]Any of upi, card, netbanking, wallet, emi. Defaults to every method enabled on your account.
customerobjectphone, email, name. A phone number pre-fills UPI collect and lets the customer see saved cards.
capturebooleanDefault true. Set false to authorise only; capture within 5 days with POST /v1/payments/{id}/capture.
expires_inintegerSeconds until the checkout link stops working. Default 900, maximum 86400.
callback_urlstringWhere the customer lands after paying. We append payment_id and status.
notesobjectUp to 15 string pairs. Returned untouched in every webhook and settlement row.
app/api/checkout/route.tsNext.js route handler
import Paydash from "@paydash/node";const paydash = new Paydash(process.env.PAYDASH_SECRET_KEY);export async function POST(req) {  const order = await req.json();  const payment = await paydash.payments.create({    amount: order.totalPaise,    currency: "INR",    methods: ["upi", "card", "netbanking"],    customer: { phone: order.phone },    callback_url: "https://kovaifresh.in/orders/done",    notes: { order_id: order.id },  }, { idempotencyKey: `order_${order.id}` });  return Response.redirect(payment.url, 303);}
201 Createdapplication/json
{  "id": "pay_Mq7Yk2Lw9Xa3",  "object": "payment",  "amount": 49900,  "currency": "INR",  "status": "created",  "methods": ["upi", "card", "netbanking"],  "customer": { "phone": "+919840012345" },  "url": "https://pay.paydash.in/Mq7Yk2Lw",  "callback_url": "https://kovaifresh.in/orders/done",  "notes": { "order_id": "7781" },  "created_at": "2026-09-04T14:17:10+05:30",  "expires_at": "2026-09-04T14:32:10+05:30",  "test": true}

To refund, call POST /v1/payments/{id}/refunds with an optional amount for a partial refund. UPI and card refunds reach the customer in 1 to 3 working days; netbanking in 3 to 5. Our fee on the original payment is not returned, and refunds themselves are free.

Install an SDK

All four SDKs are generated from the same OpenAPI document, so field names, error classes and retry behaviour match. Each one retries idempotent requests three times with jitter and raises typed errors for the codes below.

Node 18+v3.4.1
npm install @paydash/node
Python 3.9+v3.4.0
pip install paydash
Go 1.21+v3.2.2
go get github.com/paydash/paydash-go/v3
PHP 8.1+v3.3.0
composer require paydash/paydash-php

Verify a webhook

Every event is signed with your endpoint's secret. The header carries a Unix timestamp and an HMAC-SHA256 of timestamp.body. Compare in constant time, reject anything older than five minutes, and read the raw body before any JSON middleware touches it.

webhooks.jsExpress
import express from "express";import { createHmac, timingSafeEqual } from "node:crypto";const app = express();const secret = process.env.PAYDASH_WEBHOOK_SECRET;app.post("/webhooks/paydash", express.raw({ type: "application/json" }), async (req, res) => {  // Paydash-Signature: t=1725436330,v1=5f1c…9a  const header = req.get("Paydash-Signature") ?? "";  const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")));  const expected = createHmac("sha256", secret).update(`${t}.${req.body}`).digest("hex");  const valid = v1?.length === expected.length &&    timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"));  if (!valid) return res.status(400).send("bad signature");  if (Date.now() / 1000 - Number(t) > 300) return res.status(400).send("stale");  const event = JSON.parse(req.body);  if (event.type === "payment.captured") {    await orders.markPaid(event.data.notes.order_id, event.data.id);  }  res.sendStatus(200);});

The SDKs wrap the same check. In Python:

webhooks.pyFlask
from paydash import Webhook, SignatureError@app.post("/webhooks/paydash")def paydash_webhook():    try:        event = Webhook.construct_event(            request.get_data(), request.headers["Paydash-Signature"], secret        )    except SignatureError:        abort(400)    if event.type == "payment.captured":        orders.mark_paid(event.data.notes["order_id"], event.data.id)    return "", 200

Events

payment.createdCheckout link issued
payment.authorisedBank approved, not yet captured
payment.capturedMoney is yours. Fulfil the order.
payment.failedIncludes a reason code
payment.expiredLink timed out
refund.processedRefund left our escrow
settlement.createdBatch formed, UTR pending
settlement.settledUTR attached, money in your bank
mandate.activeUPI AutoPay mandate live

Failed deliveries are retried with backoff at 1 min, 5 min, 30 min, 2 h, 6 h and 24 h. Any 2xx counts as delivered. Events are also listable at GET /v1/events for 30 days, so a missed webhook is never a lost order.

Errors

Errors are JSON with type, code, message and, for declines, reason. The message is safe to log but not to show a customer; use reason for that.

statuscodemeaning
400invalid_requestA parameter is missing or malformed. The message names the field.
401authentication_errorKey is missing, revoked, or a test key on the live host.
402payment_failedDeclined. reason is one of upi_declined, insufficient_funds, bank_timeout, card_blocked, 3ds_failed.
409idempotency_conflictSame Idempotency-Key, different body.
429rate_limited600 requests a minute per key. Retry after the Retry-After header.