PipraPay Integration Skill
PipraPay is a Bangladeshi payment gateway supporting bKash, Nagad, Rocket, and card payments. It works via a redirect-based checkout model: your server creates a payment session, receives a pp_url, and redirects the user there. After payment, the user is sent to your return_url and PipraPay calls your webhook_url.
Environment Setup
| Variable | Value |
|---|---|
| Sandbox base URL | https://sandbox.piprapay.com/api |
| Production base URL | https://piprapay.com/api (confirm with PipraPay docs) |
| API Key header | MHS-PIPRAPAY-API-KEY |
Always use sandbox during development. Switch the base URL and API key for production.
The 3-Endpoint API
1. Create Payment (POST /checkout/redirect)
Initiates a new payment session and returns a redirect URL.
Request body:
{
"full_name": "Customer Name",
"email_address": "customer@email.com",
"mobile_number": "01XXXXXXXXX",
"amount": "500",
"currency": "BDT",
"metadata": "{\"order_id\": \"123\", \"product_id\": \"456\"}",
"return_url": "https://yoursite.com/payment/success",
"webhook_url": "https://yoursite.com/api/webhooks/piprapay"
}
Success response (200):
{
"pp_id": "349452200706799329851862826",
"pp_url": "https://pay.demo.com/checkout/2134123412341234231"
}
→ Redirect the user to pp_url immediately after receiving this.
Key notes:
metadatamust be a JSON-encoded string, not an object. UseJSON.stringify()orjson_encode().amountis a string, not a number.- Store
pp_idin your database immediately — you'll need it to verify and refund.
2. Verify Payment (POST /verify-payment)
Call this to confirm a payment after the user returns to your return_url, or within your webhook handler.
Request body:
{ "pp_id": "349452200706799329851862826" }
Success response (200):
{
"pp_id": "...",
"status": "completed",
"amount": "6",
"fee": "0.68",
"discount_amount": "0.34",
"total": 6.34,
"gateway": "Bkash Personal",
"sender": "01300000000",
"transaction_id": "LSKDJCVNNVHG",
"currency": "USD",
"local_currency": "BDT",
"local_net_amount": "774.99",
"metadata": { "order_id": "123" },
"date": "Jan 30, 2026 07:01 PM"
}
Always verify server-side — never trust the return URL alone. A user can manually visit your return URL without paying.
3. Refund Payment (POST /refund-payment)
Initiates a refund for a completed payment.
Request body:
{ "pp_id": "349452200706799329851862826" }
Returns the same structure as verify. Check status field in response.
On 400, the refund was rejected (already refunded, not completed, etc.).
Webhook Handling
PipraPay POSTs a JSON payload to your webhook_url when a payment status changes.
Payload shape:
{
"pp_id": "349452200706799329851862826",
"status": "completed",
"amount": "6",
"transaction_id": "LSKDJCVNNVHG",
"metadata": { "order_id": "123" }
}
Webhook handler requirements:
- Respond with HTTP 200 immediately — do heavy work async.
- Always re-verify by calling
/verify-paymentwith the receivedpp_id. Don't trust the webhook payload alone. - Use
metadata(e.g.order_id) to find and update the correct order in your DB. - Make your handler idempotent — PipraPay may retry; the same
pp_idarriving twice should not double-fulfill an order.
Implementation by Stack
For stack-specific code, read only the file matching the user's stack from references/:
- Next.js / TypeScript →
references/nextjs.md - Node.js (Express/Vanilla) →
references/nodejs.md - PHP →
references/php.md - Plain HTML + fetch (frontend-only demo) →
references/html.md
Decision Tree
User wants to integrate PipraPay?
├── What stack?
│ ├── Next.js → read references/nextjs.md
│ ├── Node.js → read references/nodejs.md
│ ├── PHP → read references/php.md
│ └── HTML/JS → read references/html.md
└── What action?
├── Create checkout → POST /checkout/redirect
├── Verify payment → POST /verify-payment (always server-side)
├── Handle webhook → POST /verify-payment inside handler
└── Refund → POST /refund-payment
Security Checklist
- API key stored in environment variable, never in frontend code
- Payment created server-side only (never expose API key to client)
-
pp_idstored in DB at checkout creation time - Payment verified server-side via
/verify-paymentbefore fulfilling order - Webhook handler re-verifies before acting on status
- Webhook handler is idempotent (safe to call multiple times with same
pp_id) - Using HTTPS for
return_urlandwebhook_urlin production
Common Mistakes
| Mistake | Fix |
|---|---|
Sending metadata as an object |
Must be JSON.stringify({...}) — a string |
Trusting return_url params without verifying |
Always call /verify-payment server-side |
Not storing pp_id before redirect |
Store it right after /checkout/redirect succeeds |
| Fulfilling order in webhook without re-verify | Call /verify-payment inside the webhook handler |
| Hardcoding API key in frontend JS | Move payment creation to a backend API route |