Accept payments with Payrapyd
Your server talks to the Payrapyd API; your customer pays on a hosted page. You never see or store card or UPI details. All amounts are integer strings in paise (₹1,250.50 = "125050").
- Create an order on your server.
- Send the customer to the hosted checkout page.
- When they come back, ask the API for the order's status, and also listen for webhooks.
- Fulfil only when the status is
succeeded.
Base URL: https://sandbox-api.payrapyd.com · Machine-readable reference: openapi.json (import it into Postman or any OpenAPI tool).
1. API keys
Create a key in the dashboard (merchant page → API keys). Send it on every request as a bearer token. Test keys start with pyd_test_ and only ever reach the payment simulator: no real money moves.
curl https://sandbox-api.payrapyd.com/v1/whoami -H "Authorization: Bearer $PAYRAPYD_KEY"Keep keys on your server only: never in a browser, app or repository. If a key leaks, revoke it in the dashboard and create a new one.
2. Create an order and send the customer to checkout
curl -X POST https://sandbox-api.payrapyd.com/v1/orders \
-H "Authorization: Bearer $PAYRAPYD_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-INV-1001" \
-d '{"amount":"125050","currency":"INR","merchantOrderRef":"INV-1001",
"returnUrl":"https://shop.example/payment/return"}'Then get the hosted checkout URL and redirect the customer to it (full page, not an iframe):
curl -X POST https://sandbox-api.payrapyd.com/v1/orders/pay_…/checkout-sessions \
-H "Authorization: Bearer $PAYRAPYD_KEY"
# → { "checkoutUrl": "https://…", "expiresAt": "…", "attemptId": "att_…" }3. Confirm the result
The customer comes back to your returnUrl with ?payrapyd_order_id=pay_…. The return never tells you whether they paid: anyone can type that URL. Always ask the API:
curl https://sandbox-api.payrapyd.com/v1/orders/pay_… -H "Authorization: Bearer $PAYRAPYD_KEY"
# "status": "succeeded" → fulfil · "pending" → show "processing", check again or wait for the webhook
# "failed" / "expired" → let the customer try again with a new orderStatuses: created → pending → succeeded | failed | expired, then partially_refunded → refunded. Only the payment provider's confirmation can make an order succeed.
4. Webhooks
Register an https endpoint (dashboard → merchant → Webhooks, or POST /v1/webhook-endpoints) and keep the signing secret it shows once. Events: order.succeeded, order.failed, order.expired, refund.succeeded, refund.failed.
Verify every delivery before trusting it, using the raw request body:
// Node.js (Express): app.post('/payrapyd/webhooks', express.raw({ type: 'application/json' }), handler)
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const given = String(parts.v1 ?? '');
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
function handler(req, res) {
if (!verify(req.body.toString('utf8'), req.get('Payrapyd-Signature') ?? '', process.env.PAYRAPYD_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// De-duplicate on event.id (also in the Payrapyd-Event-Id header), then do the work.
res.sendStatus(200);
}- Answer with any 2xx quickly; do slow work afterwards.
- Failed deliveries are retried for about 21 hours (10 s, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h).
- An event can arrive more than once or out of order: de-duplicate on its id and re-check the order if in doubt.
- Endpoints must be public https addresses; redirects are not followed.
5. Refunds
curl -X POST https://sandbox-api.payrapyd.com/v1/orders/pay_…/refunds \
-H "Authorization: Bearer $PAYRAPYD_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-INV-1001-1" \
-d '{"amount":"25050","reason":"item returned"}'Partial refunds are allowed; together they can never exceed what was paid. A refund is final only when the provider confirms it (refund.succeeded webhook, or GET /v1/refunds/rfd_…).
6. Retries, idempotency and errors
- Every POST that creates something needs an
Idempotency-Key. Retrying with the same key and body is safe: you get the original result back (Idempotent-Replayed: true). The same key with a different body is refused (409). - Errors always look like
{"error":{"code":"…","message":"…"}}. 400 = fix the request; 401 = key problem; 404 = not found (or not yours); 409 = state conflict; 429 = slow down; 5xx = retry later with the same Idempotency-Key.
7. Testing in the sandbox
- With a test key, checkout opens the payment simulator: choose success, failure or leave pending. No card or bank details.
- A refund with the reason
sim:failis declined by the simulator, so you can test that path. - You can also start test payments and refunds from the dashboard (Transactions page).
- Every test object is marked
simulated: true; webhook events carrylivemode: false.