Loading…

Developer

API keys, webhooks, and integration tools.

Overview

SecurePay is a hosted-checkout payment layer. Your server creates a checkout session for an amount, you redirect the customer to SecurePay's hosted page to pay (bKash, Nagad, Rocket, cards via SSLCommerz, and more), and SecurePay notifies your server of the result with a signed webhook. You never handle raw card or wallet credentials.

The integration in three steps

  1. Create a session server-side with your secret key → you get a hosted url.
  2. Redirect the customer to that url. They pay on SecurePay.
  3. Receive the webhook (payment.succeeded) and fulfil the order. Optionally poll the status endpoint as a fallback.

Base URL: https://api.pay.ccbd.dev — all API paths below are relative to this origin. Amounts are always in minor units (e.g. ৳6,000.00 = 600000). The default currency is BDT.

Authentication

Authenticate server-side requests with your secret key as a bearer token. Create and manage keys in the API keys tab.

http
Authorization: Bearer sk_live_your_secret_key
FieldTypeDescription
sk_live_…secretLive secret key — full server-side access. Routes to real gateways. Never expose it in client-side code.
sk_test_…secretTest secret key — same API, but payments run against the sandbox simulator (no real money). See Test mode.
pk_live_… / pk_test_…publishablePublishable key — safe for client-side checkout initiation (scope: create checkout sessions only).

The environment (test vs live) is determined by the key you use — there is no separate flag. A key's secret is shown only once at creation; store it securely.

Quickstart

A minimal end-to-end integration in Node.js:

javascript
// 1) Create a checkout session (server-side)
const res = await fetch("https://api.pay.ccbd.dev/v1/checkout-sessions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SECUREPAY_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `order-${orderId}`, // safe retries
  },
  body: JSON.stringify({
    amountMinor: 600000,          // ৳6,000.00
    currency: "BDT",
    description: `Order #${orderId}`,
    metadata: { orderId },        // echoed back in the webhook
    successUrl: "https://yourapp.com/pay/return?order=" + orderId,
    cancelUrl:  "https://yourapp.com/pay/cancel?order=" + orderId,
  }),
});
const { data } = await res.json();

// 2) Redirect the customer to the hosted checkout
redirect(data.url);

// 3) Fulfil the order when the signed webhook arrives (see Webhooks).

Create a checkout session

POST/v1/checkout-sessions

Creates a session and returns a hosted checkout url. Send the Idempotency-Key header to make retries safe (a repeated key returns the same session instead of creating a duplicate).

Request body

FieldTypeDescription
amountMinor*integerAmount in minor units (× 100). Must be positive.
currency*stringISO currency code. Currently BDT.
descriptionstringShown to the payer / on the receipt.
customerobjectOptional payer contact: { name, email, phone }.
successUrlstringWhere the payer returns after paying. Defaults to SecurePay’s hosted result page.
cancelUrlstringWhere the payer returns on cancel/failure.
metadataobjectFree-form string map echoed verbatim in webhooks — use it to correlate your order/invoice id.
invoiceIdstringOptional: link the session to your own invoice reference.

Example request

bash
curl -X POST https://api.pay.ccbd.dev/v1/checkout-sessions \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001" \
  -d '{
    "amountMinor": 600000,
    "currency": "BDT",
    "description": "Order #1001",
    "customer": { "name": "Rahim", "email": "rahim@example.com", "phone": "01700000000" },
    "successUrl": "https://yourapp.com/pay/return?order=1001",
    "cancelUrl": "https://yourapp.com/pay/cancel?order=1001",
    "metadata": { "orderId": "1001" }
  }'

Response (201)

json
{
  "data": {
    "id": "a01dc849-9d27-4faa-a590-daa0bb96efad",
    "status": "OPEN",
    "amountMinor": 600000,
    "currency": "BDT",
    "url": "https://pay.ccbd.dev/checkout/a01dc849-9d27-4faa-a590-daa0bb96efad",
    "eligibleMethods": [
      {
        "acceptanceMethodId": "a2dc7499-5c07-4914-87ab-22023039d559",
        "mode": "GATEWAY",
        "gatewayCode": "sslcommerz",
        "displayLabel": "SSLCommerz",
        "sortOrder": 1
      }
    ],
    "livemode": true,
    "expiresAt": "2026-08-11T20:44:34.243Z"
  }
}

Redirect the customer

Send the customer to data.url (a full-page redirect). What they see depends on how many payment methods you have enabled:

  • One gateway → they go straight to that gateway's page (the SecurePay picker is skipped).
  • Two or more → SecurePay shows a method picker first, then routes to the chosen gateway.

After payment, the payer is returned to your successUrl / cancelUrl. Treat this return purely as UX — the authoritative result is the webhook (the customer may close the tab before returning).

Check payment status

GET/v1/public/checkout-sessions/{id}/status

A lightweight, unauthenticated poll of a session's outcome. Use it as a fallback or to drive a “waiting for payment” screen — webhooks remain the primary settlement signal.

bash
curl https://api.pay.ccbd.dev/v1/public/checkout-sessions/a01dc849.../status
# { "data": { "status": "COMPLETED", "paymentStatus": "SUCCEEDED" } }
FieldTypeDescription
statusstringSession state: OPEN, COMPLETED, or EXPIRED.
paymentStatusstringPayment state: PENDING, SUCCEEDED, FAILED, or EXPIRED.

Webhooks

Register an HTTPS endpoint in the Webhooks tab and subscribe to events. SecurePay POSTs a signed JSON payload; a signing secret is shown once when you create the endpoint.

Request headers

FieldTypeDescription
X-SecurePay-EventstringThe event type, e.g. payment.succeeded.
X-SecurePay-Signaturestringsha256=<hex> — HMAC-SHA256 of the raw request body, keyed by your endpoint’s signing secret.
X-SecurePay-DeliverystringUnique delivery id (stable across retries of the same event).

Payload envelope

json
{
  "event": "payment.succeeded",
  "merchantId": "888e49fd-aa4f-4f9a-9c39-a413ddbb7cb9",
  "data": {
    "paymentId": "cfdfb57f-388d-4a18-8f73-d61c88f9f843",
    "checkoutSessionId": "a01dc849-9d27-4faa-a590-daa0bb96efad",
    "amountMinor": 600000,
    "currency": "BDT",
    "metadata": { "orderId": "1001" }
  }
}

Event types

FieldTypeDescription
payment.succeededeventPayment completed. data: paymentId, checkoutSessionId, amountMinor, currency, metadata.
payment.failedeventPayment failed/declined. Same data shape as payment.succeeded.
refund.succeededeventRefund completed. data: refundId, paymentId, amountMinor, status.
refund.failedeventRefund failed. Same data shape as refund.succeeded.

Verify the signature (Node.js / Express)

javascript
import express from "express";
import crypto from "crypto";

const app = express();

// IMPORTANT: verify against the RAW body bytes, not a re-serialized object.
app.post(
  "/webhooks/securepay",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("X-SecurePay-Signature") || "";
    const expected =
      "sha256=" +
      crypto
        .createHmac("sha256", process.env.SECUREPAY_WEBHOOK_SECRET)
        .update(req.body) // Buffer of the raw body
        .digest("hex");

    // Constant-time comparison
    const valid =
      signature.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
    if (!valid) return res.status(400).send("invalid signature");

    const { event, data } = JSON.parse(req.body.toString());
    if (event === "payment.succeeded") {
      // Fulfil using your own reference from data.metadata (e.g. orderId).
      fulfilOrder(data.metadata.orderId, data.paymentId);
    }

    // Respond 2xx to acknowledge. Non-2xx (or a timeout) triggers retries.
    res.status(200).send("ok");
  }
);
Delivery & retries. Deliveries retry with backoff until your endpoint returns 2xx. Always verify the signature and treat handling as idempotent — the same event may arrive more than once (dedupe on data.paymentId or X-SecurePay-Delivery). You can inspect and re-send deliveries from the Webhooks tab.

Test mode & magic amounts

Any payment made with a sk_test_ key runs against a deterministic sandbox simulator — no real gateway, no money moved. The outcome is chosen by the exact amountMinor. Any amount not in the table below simply succeeds.

FieldTypeDescription
1001৳10.01DECLINE → payment.failed
1002৳10.02TIMEOUT → stays PENDING, then EXPIRED
1003৳10.03LOST callback → still SUCCEEDED (resolved by polling)
1004৳10.04SLOW → PENDING for a few seconds, then SUCCEEDED
any other—Instant SUCCESS (happy path)

With a sk_live_ key, the same calls route to the real gateway (e.g. SSLCommerz) where the customer completes payment with a card or wallet.

Errors

Errors use standard HTTP status codes with a machine-readable code and a human-readable message.

FieldTypeDescription
401unauthorizedInvalid or revoked API key.
400bad_requestMalformed body — e.g. amountMinor not a positive integer.
422unsupported_currencyThe currency is not enabled for this merchant.
422method_unavailableThe selected method/gateway is not available for this session.
409session_not_openThe session is already completed or expired.

Endpoint reference

Secret-key endpoint (server-side) and the public payer endpoints used by the hosted checkout page.

POST/v1/checkout-sessions
GET/v1/public/checkout-sessions/{id}
GET/v1/public/checkout-sessions/{id}/methods
GET/v1/public/checkout-sessions/{id}/status
POST/v1/public/checkout-sessions/{id}/select

The /public/ endpoints are unauthenticated and are driven by SecurePay's hosted checkout page — you normally only call POST /v1/checkout-sessions and consume webhooks.