DottsPay
Quickstart

Accept your first payment

This guide takes you from zero to a verified test payment. You will create a checkout session on your server, send your customer to the DottsPay hosted checkout, confirm the payment, and listen for a webhook.
Amounts are in the smallest unit
Send every amount as an integer in the currency's subunit. For naira that is kobo, so ₦5,000 is 500000. Every currency uses 1/100 units.

1Get your API keys

Sign in and open Dashboard → Developers. You will see a test secret key (sk_test_…) and, once your account is approved for live payments, a live one (sk_live_…). Test keys never move real money, so build everything with them first.

Secret keys belong on your server only. Never put them in a browser, a mobile app or a public repository. Keep them in environment variables:

.env
DOTTSPAY_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
DOTTSPAY_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx

If you use Node, install the SDK. It has no dependencies and works on Node 18 and later.

Shell
npm install @dottspay/node

2Create a checkout session

When your customer is ready to pay, create a checkout session from your server. Send an Idempotency-Key so that a retry after a timeout never creates a second session.

curl https://dottspay.hiddekellabs.com/api/v1/checkout/sessions \
  -H "Authorization: Bearer $DOTTSPAY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "amount": 500000,
    "email": "ada@example.com",
    "callback_url": "https://yourshop.com/payment/callback",
    "metadata": { "order_id": "ord_1042" }
  }'

DottsPay returns the session. The field you need now is checkout_url.

Response
{
  "status": true,
  "message": "Checkout session created",
  "data": {
    "id": "cs_8Kq2LmT4xPz9WbN3vR6hYd",
    "checkout_url": "https://dottspay.hiddekellabs.com/pay/c/cs_8Kq2LmT4xPz9WbN3vR6hYd",
    "status": "open",
    "mode": "test",
    "amount": 500000,
    "currency": "NGN",
    "email": "ada@example.com",
    "metadata": {
      "order_id": "ord_1042"
    },
    "callback_url": "https://yourshop.com/payment/callback",
    "expires_at": "2026-10-13T09:30:12.000Z",
    "created_at": "2026-10-12T09:30:12.000Z"
  }
}

3Redirect your customer

Send your customer to checkout_url. They pick a payment method and pay on a page hosted by DottsPay, so card details never touch your server.

app.post('/checkout', async (req, res) => {
  const order = await orders.create(req.body) // your own order record

  const session = await dottspay.checkout.create(
    {
      amount: order.totalKobo,
      email: order.email,
      callback_url: 'https://yourshop.com/payment/callback',
      metadata: { order_id: order.id },
    },
    { idempotencyKey: `order-${order.id}` },
  )

  res.redirect(303, session.checkout_url)
})

After the payment, DottsPay sends the customer back to your callback_url with two query parameters:

Text
https://yourshop.com/payment/callback?reference=dp_Hk2pQx9LmT4sWb7NcY&status=success
The status in the URL is only a hint
Anyone can type a URL. Always verify the payment from your server, as in the next step, before you ship goods or give access.

4Verify the payment

Call GET /transactions/{reference} with the reference from the callback. If the payment is still pending, DottsPay checks with the payment network before it answers.

curl https://dottspay.hiddekellabs.com/api/v1/transactions/dp_Hk2pQx9LmT4sWb7NcY \
  -H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"

Only give value when all of these are true:

  • status is success.
  • amount and currency match what you expected for this order.
  • You have not already fulfilled this reference. The callback and the webhook can both arrive, so make fulfilment safe to run twice.

See the transaction object for every field.

5Handle the webhook

Customers sometimes close the tab before the redirect finishes. A webhook makes sure you still hear about the payment. Add your endpoint URL in Dashboard → Developers → Webhooks and copy the signing secret (it starts with whsec_ and is shown once).

DottsPay sends a POST with an X-DottsPay-Signature header. Check it against the raw body before you trust anything in the payload.

import express from 'express'
import { DottsPay } from '@dottspay/node'

const app = express()

// Use the raw body for this route. The signature is computed over the exact bytes.
app.post('/webhooks/dottspay', express.raw({ type: 'application/json' }), async (req, res) => {
  let event
  try {
    event = DottsPay.webhooks.constructEvent(
      req.body,
      req.get('X-DottsPay-Signature'),
      process.env.DOTTSPAY_WEBHOOK_SECRET,
    )
  } catch {
    return res.sendStatus(400)
  }

  res.sendStatus(200) // reply first, then do the work

  if (event.event === 'charge.success') {
    await orders.markPaid(event.data.metadata?.order_id, event.data.reference)
  }
})

The webhooks guide covers retries, every event type, and verification in PHP and plain Node.

Node SDK

The examples above use @dottspay/node. It wraps every endpoint with full TypeScript types, turns API errors into a typed DottsPayError, and verifies webhook signatures. Prefer another language? Every endpoint is plain HTTPS and JSON, so cURL examples translate directly.

Read the SDK guide.

Go live

  • Finish account verification in the dashboard so live mode is switched on.
  • Swap sk_test_… for sk_live_… in your production environment.
  • Add a live webhook URL. Test and live have separate URLs and separate secrets.
  • Make one small real payment end to end, then refund it.

Next steps