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
- Get test keys from the dashboard. Secret keys start with
sk_test_, publishable keys withpk_test_. Live keys usesk_live_and only work from allow-listed IPs. - Install an SDK, or call the REST API directly with basic auth (the key is the username, the password is empty).
- Create a payment from your server with the amount in paise. The response carries a
url. - Send the customer to that URL, or open it in the SDK sheet on mobile. In the sandbox, UPI ID
success@paydashapproves andfailure@paydashdeclines; card 4111 1111 1111 1111 with any future expiry approves. - Handle
payment.capturedon 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.
curl https://api.paydash.in/v1/payments/pay_Mq7Yk2Lw9Xa3 \ -u sk_test_3QwK9xLm2Vb8: \ -H "Paydash-Version: 2026-08-01"Create a payment
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.
| parameter | type | description |
|---|---|---|
| amount * | integer | Amount in paise. ₹499.00 is 49900. Minimum 100. |
| currency * | string | INR, or USD, EUR, GBP, AED, SGD for international cards. Settlement is always in INR. |
| methods | string[] | Any of upi, card, netbanking, wallet, emi. Defaults to every method enabled on your account. |
| customer | object | phone, email, name. A phone number pre-fills UPI collect and lets the customer see saved cards. |
| capture | boolean | Default true. Set false to authorise only; capture within 5 days with POST /v1/payments/{id}/capture. |
| expires_in | integer | Seconds until the checkout link stops working. Default 900, maximum 86400. |
| callback_url | string | Where the customer lands after paying. We append payment_id and status. |
| notes | object | Up to 15 string pairs. Returned untouched in every webhook and settlement row. |
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);}{ "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.
npm install @paydash/nodepip install paydashgo get github.com/paydash/paydash-go/v3composer require paydash/paydash-phpVerify 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.
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:
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 "", 200Events
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.
| status | code | meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed. The message names the field. |
| 401 | authentication_error | Key is missing, revoked, or a test key on the live host. |
| 402 | payment_failed | Declined. reason is one of upi_declined, insufficient_funds, bank_timeout, card_blocked, 3ds_failed. |
| 409 | idempotency_conflict | Same Idempotency-Key, different body. |
| 429 | rate_limited | 600 requests a minute per key. Retry after the Retry-After header. |