Quickstart
Accept your first test payment in a few minutes — no real money, no payment account.
The whole idea#
- Use test mode. It needs no bKash or Nagad account.
- Create a test secret key in the dashboard.
- The secret key lives on your server, never in browser or app code.
- Create a Payment from your server.
- Open its
checkoutUrlfor the customer. - Simulate success with one call.
- Verify the webhook PayTaka sends your server.
Building a mobile app? Your app talks to your small backend, and that backend talks to PayTaka — start with your platform below.
Choose your platform#
The steps below are for Node.js, Next.js and any server that speaks HTTP. Other platforms follow the same six steps in their own guide.
1. Create a test secret key#
In the dashboard open Developers → API keys → Create secret key, choose Test, and copy the key (it starts with pt_test_sk_). It is shown once. Keep it on your server:
export PAYTAKA_SECRET_KEY=pt_test_sk_...
export PAYTAKA_API_URL=https://api.paytaka.live2. Get the SDK (or use REST)#
Node.js
Next.js
You can always skip the SDK and call the REST API directly — the steps are identical.
3. Create a payment#
// @paytaka/sdk quickstart — run on your SERVER (Node.js 18+).
import { PayTaka } from "@paytaka/sdk";
const paytaka = new PayTaka({
secretKey: process.env.PAYTAKA_SECRET_KEY,
baseUrl: process.env.PAYTAKA_API_URL, // @preview-only — not needed once the SDK ships with the production URL built in
});
const payment = await paytaka.payments.create({
amount: 500,
});
console.log(payment.checkoutUrl);amount: 500 is fine for a simple value. For an exact amount such as ৳500.50, pass a string: amount: "500.50". The SDK adds a secure idempotency key for you, so a retried request never creates a second payment.
Prefer plain HTTP?
#!/usr/bin/env bash
# Create a TEST payment with plain curl. Needs a Test secret key from
# Developers → API keys (it starts with pt_test_sk_). No real money is involved.
set -euo pipefail
: "${PAYTAKA_SECRET_KEY:?Set PAYTAKA_SECRET_KEY to your pt_test_sk_... key}"
: "${PAYTAKA_API_URL:?Set PAYTAKA_API_URL to the PayTaka API base URL you were given}"
curl "$PAYTAKA_API_URL/v1/payments" \
-X POST \
-H "Authorization: Bearer $PAYTAKA_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":"500.00"}'4. Open the checkoutUrl#
Send your customer to payment.checkoutUrl: redirect them from your server, or return it to your mobile app to open (Android, iOS). The test checkout page shows a TEST MODE banner and a fake receiving number.
5. Simulate the payment#
No real money moves in test mode. Complete the payment with one call — or press Simulate successful payment on the test checkout page:
await paytaka.testHelpers.payments.succeed(payment.id)6. Receive the webhook#
In Developers → Webhooks → Add endpoint register your server's URL for the Test environment and copy its signing secret (whsec_…) into PAYTAKA_WEBHOOK_SECRET.
// Receive PayTaka webhooks with @paytaka/sdk (Node.js 18+, no framework).
import { createServer } from "node:http";
import { PayTakaSignatureVerificationError, verifyWebhook } from "@paytaka/sdk";
// Demo only: PayTaka delivers each event AT LEAST once, so remember the ids you have handled.
// In production use your database (a table with a unique event_id) instead of a Set.
const processed = new Set();
createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/paytaka/webhook") {
res.writeHead(404).end();
return;
}
// 1. Read the RAW body — do not JSON.parse before verifying.
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const rawBody = Buffer.concat(chunks);
// 2. Verify the signature. Never trust an unverified webhook.
let event;
try {
event = verifyWebhook(rawBody, req.headers["paytaka-signature"], process.env.PAYTAKA_WEBHOOK_SECRET);
} catch (error) {
if (error instanceof PayTakaSignatureVerificationError) {
res.writeHead(400).end();
return;
}
throw error;
}
// 3. Already handled this event? Acknowledge and stop — never fulfil twice.
if (processed.has(event.id)) {
res.writeHead(200).end();
return;
}
// 4. Fulfil ONLY from a verified payment.paid event. Ignore anything you don't recognise.
if (event.type === "payment.paid") {
console.log("Fulfil the order for payment", event.data.object.id);
}
processed.add(event.id);
// 5. Any 2xx acknowledges the event. Don't rely on delivery order.
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ received: true }));
}).listen(Number(process.env.PORT ?? 3000));The example verifies the signature, ignores an event it has already handled, and fulfils only from payment.paid. Details: Webhooks.
You're done#
If everything worked you now have:
| Where | What you see |
|---|---|
Your console.log | a checkoutUrl |
| The payment | status: PAID after step 5 |
| Your webhook endpoint | a verified payment.paid event with livemode: false |
| The dashboard → Developers → Webhooks | the delivery marked Delivered |
That is a complete integration. Next: Going live when you're ready for real payments, or Payments and Checkout for the details.