DottsPay API v1
Base URL
https://dottspay.hiddekellabs.com/api/v1All requests must use HTTPS. Request and response bodies are JSON.
Authentication
Send your secret key as a bearer token on every request. Find your keys in Dashboard → Developers.
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxx| Key | Use |
|---|---|
sk_test_… | Test mode. Never moves real money. Objects you create are test objects. |
sk_live_… | Live mode. Real payments. Works once your account is approved. |
pk_… | Public key. Not needed for server calls. Never send a secret key to a browser. |
sk_test_…- UseTest mode. Never moves real money. Objects you create are test objects.
sk_live_…- UseLive mode. Real payments. Works once your account is approved.
pk_…- UsePublic key. Not needed for server calls. Never send a secret key to a browser.
Test and live data are separate. A test key cannot see live transactions and the other way round. See Test mode.
Responses
Every response uses the same envelope. On success, status is true and the result is in data.
{
"status": true,
"message": "Checkout session created",
"data": {
"id": "cs_8Kq2LmT4xPz9WbN3vR6hYd",
"…": "…"
}
}On failure, the HTTP status is 4xx or 5xx, status is false, and code tells you what went wrong in a form your code can check.
{
"status": false,
"message": "amount: Too small: expected number to be >=100",
"code": "validation_error"
}Errors
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | The secret key is missing, malformed, revoked or wrong. |
merchant_suspended | 403 | Your account is suspended. Contact support. |
merchant_not_live | 403 | You used a live key before live mode was switched on for your account. |
not_found | 404 | The object does not exist, or belongs to the other mode (test or live). |
invalid_json | 400 | The request body is not valid JSON. |
validation_error | 422 | A parameter is missing or invalid. The message names the field. |
idempotency_mismatch | 422 | You reused an Idempotency-Key with a different request body. |
currency_unavailable | 422 | That currency is not enabled for your account. |
rate_limited | 429 | More than 120 requests in a minute with one key. Wait and retry. |
server_error | 500 | Something went wrong on our side. It is safe to retry with the same Idempotency-Key. |
unauthorized- HTTP401
- MeaningThe secret key is missing, malformed, revoked or wrong.
merchant_suspended- HTTP403
- MeaningYour account is suspended. Contact support.
merchant_not_live- HTTP403
- MeaningYou used a live key before live mode was switched on for your account.
not_found- HTTP404
- MeaningThe object does not exist, or belongs to the other mode (test or live).
invalid_json- HTTP400
- MeaningThe request body is not valid JSON.
validation_error- HTTP422
- MeaningA parameter is missing or invalid. The message names the field.
idempotency_mismatch- HTTP422
- MeaningYou reused an Idempotency-Key with a different request body.
currency_unavailable- HTTP422
- MeaningThat currency is not enabled for your account.
rate_limited- HTTP429
- MeaningMore than 120 requests in a minute with one key. Wait and retry.
server_error- HTTP500
- MeaningSomething went wrong on our side. It is safe to retry with the same Idempotency-Key.
Retry rate_limited and server_error with a backoff. Do not retry other 4xx errors without changing the request.
Amounts
Amounts are integers in the currency's subunit. Every currency uses 1/100 units, so for NGN, ₦5,000 is 500000 kobo, and for USD, $12.50 is 1250 cents. Never send decimals.
Idempotency
Networks fail. To retry a request safely, send an Idempotency-Key header with a unique value, such as your own order ID. It is supported on POST /checkout/sessions, POST /refunds and POST /payment-links.
Idempotency-Key: order-1042If DottsPay has already seen that key with the same body, it returns the original response without doing the work again, and adds the header Idempotent-Replayed: true. Reusing a key with a different body returns 422 idempotency_mismatch.
Pagination
List endpoints take perPage (default 50, max 100) and page (starting at 1). They return the items in data and the totals in meta.
{
"status": true,
"message": "OK",
"data": [
{
"…": "…"
}
],
"meta": {
"page": 2,
"perPage": 50,
"total": 137
}
}Rate limits
Each secret key can make 120 requests per minute. Past that, requests fail with 429 rate_limited until the minute rolls over.
new DottsPay(process.env.DOTTSPAY_SECRET_KEY). SDK methods return the contents of data directly.Checkout sessions
A checkout session is a hosted payment page for one payment. Create it on your server, redirect the customer to checkout_url, then verify the transaction when they return.
The checkout object
idstring- Unique ID, starts with
cs_. checkout_urlstring- The hosted page to send your customer to.
statusstringopen,completedorexpired.modestringtestorlive.amountinteger- Amount in the subunit.
currencystring- ISO currency code, for example NGN.
emailstring | null- Customer email, if you sent one.
metadataobject | null- Your own key-value data, returned as you sent it.
callback_urlstring | null- Where the customer goes after paying.
expires_atstring- ISO 8601 time after which the session can no longer be paid.
created_atstring- ISO 8601 creation time.
amountintegerRequired- Amount in the subunit. Minimum
100. currencystringOptional- ISO currency code. Default
NGN. emailstringOptional- Customer email. If you leave it out, the customer enters it on the checkout page.
namestringOptional- Customer name.
phonestringOptional- Customer phone number.
callback_urlstringOptional- After payment, DottsPay redirects to this URL with
?reference=dp_…&status=successorstatus=failed. Always verify the reference on your server. metadataobjectOptional- Any JSON object. Copied onto the transaction and returned in webhooks.
curl -X POST "https://dottspay.hiddekellabs.com/api/v1/checkout/sessions" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY" \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"email": "ada@example.com",
"callback_url": "https://yourshop.com/payment/callback",
"metadata": {
"order_id": "ord_1042"
}
}'const session = await dottspay.checkout.create(
{
amount: 500000,
email: 'ada@example.com',
callback_url: 'https://yourshop.com/payment/callback',
metadata: { order_id: 'ord_1042' },
},
{ idempotencyKey: 'order-1042' },
)
// Redirect the customer to session.checkout_url{
"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"
}
}idstringRequired- The session ID, starting with
cs_.
curl "https://dottspay.hiddekellabs.com/api/v1/checkout/sessions/cs_8Kq2LmT4xPz9WbN3vR6hYd" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const session = await dottspay.checkout.retrieve('cs_8Kq2LmT4xPz9WbN3vR6hYd'){
"status": true,
"message": "OK",
"data": {
"id": "cs_8Kq2LmT4xPz9WbN3vR6hYd",
"checkout_url": "https://dottspay.hiddekellabs.com/pay/c/cs_8Kq2LmT4xPz9WbN3vR6hYd",
"status": "completed",
"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"
}
}Transactions
A transaction is one payment attempt. It is created when a customer starts paying through a checkout session or a payment link.
The transaction object
idstring- Unique ID.
referencestring- Your handle for this payment, starts with
dp_. Use it to verify and refund. statusstringpending,success,failed,abandonedorreversed.modestringtestorlive.amountinteger- The amount you asked for, in the subunit.
charged_amountinteger- What the customer paid. Higher than
amountwhen the customer bears the fee. feesinteger- Total fees for this payment.
netinteger- What you receive after fees.
currencystring- ISO currency code.
channelstring | null- How the customer paid, for example
cardorbank_transfer. fee_bearerstringmerchantorcustomer.settlementstringsplit(paid straight to your bank),scheduledoron_demand.customerobjectemail,nameandphone. Any of them can be null.custom_fieldsobject | null- Answers to custom fields on a payment link.
metadataobject | null- The metadata from the checkout session.
payment_link_idstring | null- Set when the payment came through a payment link.
checkout_session_idstring | null- Set when the payment came through a checkout session.
failure_reasonstring | null- Why the payment failed, when it did.
paid_atstring | null- ISO 8601 time the payment succeeded.
created_atstring- ISO 8601 creation time.
statusstringOptional- Only this status:
pending,success,failed,abandonedorreversed. fromstringOptional- ISO date. Only transactions created on or after it.
tostringOptional- ISO date. Only transactions created on or before it.
perPageintegerOptional- Results per page. Default 50, maximum 100.
pageintegerOptional- Page number, starting at 1. Default 1.
curl "https://dottspay.hiddekellabs.com/api/v1/transactions?status=success&from=2026-10-01&perPage=20" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const { data, meta } = await dottspay.transactions.list({
status: 'success',
from: '2026-10-01',
perPage: 20,
}){
"status": true,
"message": "OK",
"data": [
{
"id": "cm2x9k1f00004l708qv3t2abc",
"reference": "dp_Hk2pQx9LmT4sWb7NcY",
"status": "success",
"mode": "test",
"amount": 500000,
"charged_amount": 500000,
"fees": 7500,
"net": 492500,
"currency": "NGN",
"channel": "card",
"fee_bearer": "merchant",
"settlement": "split",
"customer": {
"email": "ada@example.com",
"name": "Ada Obi",
"phone": "+2348012345678"
},
"custom_fields": null,
"metadata": {
"order_id": "ord_1042"
},
"payment_link_id": null,
"checkout_session_id": "cs_8Kq2LmT4xPz9WbN3vR6hYd",
"failure_reason": null,
"paid_at": "2026-10-12T09:31:04.000Z",
"created_at": "2026-10-12T09:30:12.000Z"
}
],
"meta": {
"page": 1,
"perPage": 50,
"total": 1
}
}Verify a transaction
/transactions/{reference}Fetch a transaction by reference. If it is still pending, DottsPay checks with the payment network first, so the answer is as fresh as it can be. Call this before you give value.
referencestringRequired- The reference, starting with
dp_.
curl "https://dottspay.hiddekellabs.com/api/v1/transactions/dp_Hk2pQx9LmT4sWb7NcY" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const tx = await dottspay.transactions.verify('dp_Hk2pQx9LmT4sWb7NcY')
if (tx.status === 'success' && tx.amount === 500000) {
// give value
}{
"status": true,
"message": "Transaction retrieved",
"data": {
"id": "cm2x9k1f00004l708qv3t2abc",
"reference": "dp_Hk2pQx9LmT4sWb7NcY",
"status": "success",
"mode": "test",
"amount": 500000,
"charged_amount": 500000,
"fees": 7500,
"net": 492500,
"currency": "NGN",
"channel": "card",
"fee_bearer": "merchant",
"settlement": "split",
"customer": {
"email": "ada@example.com",
"name": "Ada Obi",
"phone": "+2348012345678"
},
"custom_fields": null,
"metadata": {
"order_id": "ord_1042"
},
"payment_link_id": null,
"checkout_session_id": "cs_8Kq2LmT4xPz9WbN3vR6hYd",
"failure_reason": null,
"paid_at": "2026-10-12T09:31:04.000Z",
"created_at": "2026-10-12T09:30:12.000Z"
}
}Refunds
Refund a successful transaction in full or in part. You can make several partial refunds until the full amount is used up.
The refund object
idstring- Unique ID.
transaction_referencestring- The
dp_reference of the refunded transaction. amountinteger- Refunded amount in the subunit.
currencystring- ISO currency code.
statusstringpending,processedorfailed.reasonstring | null- The reason you gave.
created_atstring- ISO 8601 creation time.
referencestringRequired- The
dp_reference of a successful transaction. amountintegerOptional- Amount to refund in the subunit. Default: everything not yet refunded.
reasonstringOptional- A note for your records.
curl -X POST "https://dottspay.hiddekellabs.com/api/v1/refunds" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY" \
-H "Idempotency-Key: refund-ord_1042-1" \
-H "Content-Type: application/json" \
-d '{
"reference": "dp_Hk2pQx9LmT4sWb7NcY",
"amount": 200000,
"reason": "Customer returned one item"
}'const refund = await dottspay.refunds.create(
{ reference: 'dp_Hk2pQx9LmT4sWb7NcY', amount: 200000, reason: 'Customer returned one item' },
{ idempotencyKey: 'refund-ord_1042-1' },
){
"status": true,
"message": "Refund created",
"data": {
"id": "cm2xa3b7n0009l708h4kd9xyz",
"transaction_reference": "dp_Hk2pQx9LmT4sWb7NcY",
"amount": 200000,
"currency": "NGN",
"status": "pending",
"reason": "Customer returned one item",
"created_at": "2026-10-12T14:02:45.000Z"
}
}Payment links
A payment link is a reusable page you can share anywhere. Each payment through it creates a transaction.
The payment link object
idstring- Unique ID.
urlstring- The public page to share.
slugstring- The last part of the URL.
modestringtestorlive.titlestring- Shown at the top of the page.
descriptionstring | null- Shown under the title.
amount_typestringfixed(you set the price) orflexible(the customer enters an amount).amountinteger | null- The price in the subunit, for
fixedlinks. min_amountinteger | null- The lowest amount allowed, for
flexiblelinks. currencystring- ISO currency code.
activeboolean- Inactive links cannot take payments.
sold_countinteger- Number of successful payments.
stock_limitinteger | null- Stop taking payments once
sold_countreaches this. expires_atstring | null- ISO 8601 time after which the link stops taking payments.
redirect_urlstring | null- Where to send the customer after a successful payment.
created_atstring- ISO 8601 creation time.
List payment links
/payment-linksperPageintegerOptional- Results per page. Default 50, maximum 100.
pageintegerOptional- Page number, starting at 1. Default 1.
curl "https://dottspay.hiddekellabs.com/api/v1/payment-links?perPage=20" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const { data, meta } = await dottspay.paymentLinks.list({ perPage: 20 }){
"status": true,
"message": "OK",
"data": [
{
"id": "cm2xb8p2q000cl708fz1m3qrs",
"url": "https://dottspay.hiddekellabs.com/pay/k3m9x2pq",
"slug": "k3m9x2pq",
"mode": "test",
"title": "Small chops tray",
"description": "Party pack for 20 guests",
"amount_type": "fixed",
"amount": 2500000,
"min_amount": null,
"currency": "NGN",
"active": true,
"sold_count": 0,
"stock_limit": 50,
"expires_at": null,
"redirect_url": "https://yourshop.com/thanks",
"created_at": "2026-10-12T10:15:00.000Z"
}
],
"meta": {
"page": 1,
"perPage": 50,
"total": 1
}
}titlestringRequired- What the customer is paying for.
descriptionstringOptional- More detail, shown under the title.
amount_typestringOptionalfixedorflexible. Defaultfixed.amountintegerOptional- Price in the subunit. Required when
amount_typeisfixed. min_amountintegerOptional- Lowest amount a customer can pay on a
flexiblelink. currencystringOptional- ISO currency code. Default
NGN. redirect_urlstringOptional- Where to send the customer after a successful payment.
stock_limitintegerOptional- Maximum number of successful payments.
expires_atstringOptional- ISO 8601 time when the link stops taking payments.
curl -X POST "https://dottspay.hiddekellabs.com/api/v1/payment-links" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY" \
-H "Idempotency-Key: link-small-chops" \
-H "Content-Type: application/json" \
-d '{
"title": "Small chops tray",
"description": "Party pack for 20 guests",
"amount": 2500000,
"stock_limit": 50,
"redirect_url": "https://yourshop.com/thanks"
}'const link = await dottspay.paymentLinks.create(
{
title: 'Small chops tray',
description: 'Party pack for 20 guests',
amount: 2500000, // ₦25,000
stock_limit: 50,
redirect_url: 'https://yourshop.com/thanks',
},
{ idempotencyKey: 'link-small-chops' },
)
console.log(link.url){
"status": true,
"message": "Payment link created",
"data": {
"id": "cm2xb8p2q000cl708fz1m3qrs",
"url": "https://dottspay.hiddekellabs.com/pay/k3m9x2pq",
"slug": "k3m9x2pq",
"mode": "test",
"title": "Small chops tray",
"description": "Party pack for 20 guests",
"amount_type": "fixed",
"amount": 2500000,
"min_amount": null,
"currency": "NGN",
"active": true,
"sold_count": 0,
"stock_limit": 50,
"expires_at": null,
"redirect_url": "https://yourshop.com/thanks",
"created_at": "2026-10-12T10:15:00.000Z"
}
}Retrieve a payment link
/payment-links/{id}idstringRequired- The payment link ID.
curl "https://dottspay.hiddekellabs.com/api/v1/payment-links/cm2xb8p2q000cl708fz1m3qrs" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const link = await dottspay.paymentLinks.retrieve('cm2xb8p2q000cl708fz1m3qrs'){
"status": true,
"message": "OK",
"data": {
"id": "cm2xb8p2q000cl708fz1m3qrs",
"url": "https://dottspay.hiddekellabs.com/pay/k3m9x2pq",
"slug": "k3m9x2pq",
"mode": "test",
"title": "Small chops tray",
"description": "Party pack for 20 guests",
"amount_type": "fixed",
"amount": 2500000,
"min_amount": null,
"currency": "NGN",
"active": true,
"sold_count": 12,
"stock_limit": 50,
"expires_at": null,
"redirect_url": "https://yourshop.com/thanks",
"created_at": "2026-10-12T10:15:00.000Z"
}
}Update a payment link
/payment-links/{id}Send only the fields you want to change. To stop taking payments, set active to false.
titlestringOptional- New title.
descriptionstringOptional- New description.
activebooleanOptional- Turn the link on or off.
redirect_urlstringOptional- New redirect URL.
stock_limitintegerOptional- New stock limit.
expires_atstringOptional- New expiry time, ISO 8601.
curl -X PATCH "https://dottspay.hiddekellabs.com/api/v1/payment-links/cm2xb8p2q000cl708fz1m3qrs" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"active": false
}'const link = await dottspay.paymentLinks.update('cm2xb8p2q000cl708fz1m3qrs', { active: false }){
"status": true,
"message": "Payment link updated",
"data": {
"id": "cm2xb8p2q000cl708fz1m3qrs",
"url": "https://dottspay.hiddekellabs.com/pay/k3m9x2pq",
"slug": "k3m9x2pq",
"mode": "test",
"title": "Small chops tray",
"description": "Party pack for 20 guests",
"amount_type": "fixed",
"amount": 2500000,
"min_amount": null,
"currency": "NGN",
"active": false,
"sold_count": 12,
"stock_limit": 50,
"expires_at": null,
"redirect_url": "https://yourshop.com/thanks",
"created_at": "2026-10-12T10:15:00.000Z"
}
}Customers
DottsPay records a customer the first time an email address pays you.
idstring- Unique ID.
emailstring- Email address.
namestring | null- Name, if known.
phonestring | null- Phone number, if known.
created_atstring- ISO 8601 creation time.
List customers
/customersperPageintegerOptional- Results per page. Default 50, maximum 100.
pageintegerOptional- Page number, starting at 1. Default 1.
curl "https://dottspay.hiddekellabs.com/api/v1/customers" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const { data, meta } = await dottspay.customers.list(){
"status": true,
"message": "OK",
"data": [
{
"id": "cm2x9k1ez0002l708m6p0aaaa",
"email": "ada@example.com",
"name": "Ada Obi",
"phone": "+2348012345678",
"created_at": "2026-10-12T09:31:04.000Z"
}
],
"meta": {
"page": 1,
"perPage": 50,
"total": 1
}
}Banks
Look up bank codes and confirm account names, for example before you save a customer's payout details.
List banks
/bankscurrencystringOptional- ISO currency code. Default
NGN.
curl "https://dottspay.hiddekellabs.com/api/v1/banks?currency=NGN" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const banks = await dottspay.banks.list('NGN'){
"status": true,
"message": "OK",
"data": [
{
"code": "044",
"name": "Access Bank"
},
{
"code": "058",
"name": "Guaranty Trust Bank"
},
{
"code": "999992",
"name": "OPay"
},
{
"code": "…",
"name": "…"
}
]
}Resolve an account number
/banks/resolveaccount_numberstringRequired- The 10-digit account number.
bank_codestringRequired- The bank's
codefrom List banks.
curl "https://dottspay.hiddekellabs.com/api/v1/banks/resolve?account_number=0123456789&bank_code=058" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const { account_name } = await dottspay.banks.resolve('0123456789', '058'){
"status": true,
"message": "OK",
"data": {
"account_name": "ADA OBI"
}
}Balance
Retrieve your balance
/balanceReturns one entry per currency. This only matters for pooled settlement (scheduled or on_demand). With split settlement, money goes straight to your bank and these numbers stay at zero.
currencystring- ISO currency code.
availableinteger- Ready to be paid out, in the subunit.
pending_payoutinteger- Already queued for payout, in the subunit.
curl "https://dottspay.hiddekellabs.com/api/v1/balance" \
-H "Authorization: Bearer $DOTTSPAY_SECRET_KEY"const balances = await dottspay.balance.retrieve(){
"status": true,
"message": "OK",
"data": [
{
"currency": "NGN",
"available": 18450000,
"pending_payout": 5000000
}
]
}