Quick start
This takes about five minutes. Everything happens in test mode: no money moves.
-
Get your test keys.
Sign up at the dashboard. Your test keys are issued straight away, before any business checks, and shown on the next screen. Copy the secret key (
sk_test_…).The secret key is shown only once. If you did not save it, open Developers → API keys and press Roll secret key to get a new one.
Put it in an environment variable, not in your code:
Terminal window export NABLR_SECRET_KEY=sk_test_your_key_here -
Check the key works.
Terminal window curl https://pwn-api.142-93-42-70.sslip.io/v1/whoami \-H "Authorization: Bearer $NABLR_SECRET_KEY"You get back your business name and
"mode": "test". -
Initialise a payment.
From your server, create a ₦5,000 payment for order 1042. Amounts are in kobo, so ₦5,000.00 is
500000.initialise.sh #!/usr/bin/env bash# Start a ₦5,000 payment for order 1042.# Run: NABLR_SECRET_KEY=sk_test_… bash initialise.sh order_1042REFERENCE="${1:-order_1042}"curl -sS https://pwn-api.142-93-42-70.sslip.io/v1/payments \-H "Authorization: Bearer $NABLR_SECRET_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: ${REFERENCE}_attempt_1" \-d '{"amount": 500000,"currency": "NGN","reference": "'"$REFERENCE"'","customer": { "email": "ada@example.com", "name": "Ada Obi" },"callback_url": "https://shop.example/thanks","metadata": { "order_id": 1042 }}'initialise.mjs // Start a ₦5,000 payment for order 1042 and print the checkout URL.// Node 18 or later. Run: NABLR_SECRET_KEY=sk_test_… node initialise.mjs order_1042const reference = process.argv[2] ?? "order_1042";const res = await fetch("https://pwn-api.142-93-42-70.sslip.io/v1/payments", {method: "POST",headers: {Authorization: `Bearer ${process.env.NABLR_SECRET_KEY}`,"Content-Type": "application/json",// The same key on a retry returns the first payment instead of a second one."Idempotency-Key": `${reference}_attempt_1`,},body: JSON.stringify({amount: 500000, // kobo: ₦5,000.00currency: "NGN",reference,customer: { email: "ada@example.com", name: "Ada Obi" },callback_url: "https://shop.example/thanks",metadata: { order_id: 1042 },}),});const body = await res.json();if (!res.ok) {console.error(`${res.status} ${body.error.code}: ${body.error.message}`);process.exit(1);}// Send the customer here.console.log(body.data.checkout_url);initialise.php <?php// Start a ₦5,000 payment for order 1042 and print the checkout URL.// Run: NABLR_SECRET_KEY=sk_test_… php initialise.php order_1042$reference = $argv[1] ?? "order_1042";$ch = curl_init("https://pwn-api.142-93-42-70.sslip.io/v1/payments");curl_setopt_array($ch, [CURLOPT_POST => true,CURLOPT_RETURNTRANSFER => true,CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("NABLR_SECRET_KEY"),"Content-Type: application/json",// The same key on a retry returns the first payment instead of a second one."Idempotency-Key: {$reference}_attempt_1",],CURLOPT_POSTFIELDS => json_encode(["amount" => 500000, // kobo: ₦5,000.00"currency" => "NGN","reference" => $reference,"customer" => ["email" => "ada@example.com", "name" => "Ada Obi"],"callback_url" => "https://shop.example/thanks","metadata" => ["order_id" => 1042],]),]);$body = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);if ($status !== 201 && $status !== 200) {fwrite(STDERR, "$status {$body['error']['code']}: {$body['error']['message']}\n");exit(1);}// Send the customer here, e.g. header("Location: " . $body["data"]["checkout_url"]);echo $body["data"]["checkout_url"], "\n";initialise.py # Start a ₦5,000 payment for order 1042 and print the checkout URL.# Python 3.8 or later, standard library only.# Run: NABLR_SECRET_KEY=sk_test_… python3 initialise.py order_1042import jsonimport osimport sysimport urllib.errorimport urllib.requestreference = sys.argv[1] if len(sys.argv) > 1 else "order_1042"request = urllib.request.Request("https://pwn-api.142-93-42-70.sslip.io/v1/payments",method="POST",headers={"Authorization": f"Bearer {os.environ['NABLR_SECRET_KEY']}","Content-Type": "application/json",# The same key on a retry returns the first payment instead of a second one."Idempotency-Key": f"{reference}_attempt_1",},data=json.dumps({"amount": 500000, # kobo: ₦5,000.00"currency": "NGN","reference": reference,"customer": {"email": "ada@example.com", "name": "Ada Obi"},"callback_url": "https://shop.example/thanks","metadata": {"order_id": 1042},}).encode(),)try:with urllib.request.urlopen(request, timeout=15) as response:body = json.load(response)except urllib.error.HTTPError as e:error = json.load(e)["error"]sys.exit(f"{e.code} {error['code']}: {error['message']}")# Send the customer here.print(body["data"]["checkout_url"])The response is the payment, in
pending, with acheckout_url:{"data": {"id": "pay_01M3XX8ERVFWYRPQA68NCA8FQ3","object": "payment","reference": "order_1042","status": "pending","mode": "test","amount": 500000,"currency": "NGN","fee": 7500,"fee_bearer": "merchant","charged_amount": 500000,"net": 492500,"checkout_url": "https://pwn-checkout.142-93-42-70.sslip.io/c/ck_test_by1k2G3kGkaUO5UMHGYUAWqZAha3uAM8pozrWeozlcq","checkout_token": "ck_test_by1k2G3kGkaUO5UMHGYUAWqZAha3uAM8pozrWeozlcq","expires_at": "2026-10-02T09:56:25Z","created_at": "2026-10-02T08:56:25Z"},"meta": { "request_id": "01a0fbd4-3b18-7181-9149-8dbcae100085" }}(Some fields are left out here. The reference lists them all.)
Run it again with the same reference and you get the same payment back, because the
Idempotency-Keyis the same. A new order needs a new reference. -
Pay it.
Open
checkout_urlin your browser. Choose Card and enter:Card number Expiry CVV 4084 0840 8408 4081any future date any 3 digits The checkout says Payment successful and, because you sent a
callback_url, sends you on tohttps://shop.example/thanks?reference=order_1042.Test mode lists cards and amounts for every other outcome.
-
Verify it.
Never trust the redirect alone: anyone can type that URL. Ask the API.
verify.sh #!/usr/bin/env bash# Check a payment before you give value.# Run: NABLR_SECRET_KEY=sk_test_… bash verify.sh order_1042REFERENCE="${1:-order_1042}"curl -sS "https://pwn-api.142-93-42-70.sslip.io/v1/payments/$REFERENCE" \-H "Authorization: Bearer $NABLR_SECRET_KEY"# Give value only if data.status is "succeeded" and data.amount and# data.currency are what you charged.verify.mjs // Check a payment before you give value.// Node 18 or later. Run: NABLR_SECRET_KEY=sk_test_… node verify.mjs order_1042const reference = process.argv[2] ?? "order_1042";const expectedAmount = 500000; // what your order says, in koboconst res = await fetch(`https://pwn-api.142-93-42-70.sslip.io/v1/payments/${encodeURIComponent(reference)}`, {headers: { Authorization: `Bearer ${process.env.NABLR_SECRET_KEY}` },});const body = await res.json();if (!res.ok) {console.error(`${res.status} ${body.error.code}: ${body.error.message}`);process.exit(1);}const payment = body.data;if (payment.status === "succeeded" && payment.amount === expectedAmount && payment.currency === "NGN") {// Give value once: record payment.id so a second check does nothing.console.log(`paid: ${payment.id}`);} else if (payment.status === "pending" || payment.status === "processing") {// Not failed. Wait for the webhook, or check again in a minute.console.log(`not yet: ${payment.status}`);} else {console.log(`not paid: ${payment.status}`);}verify.php <?php// Check a payment before you give value.// Run: NABLR_SECRET_KEY=sk_test_… php verify.php order_1042$reference = $argv[1] ?? "order_1042";$expectedAmount = 500000; // what your order says, in kobo$ch = curl_init("https://pwn-api.142-93-42-70.sslip.io/v1/payments/" . rawurlencode($reference));curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("NABLR_SECRET_KEY")],]);$body = json_decode(curl_exec($ch), true);if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {fwrite(STDERR, "{$body['error']['code']}: {$body['error']['message']}\n");exit(1);}$payment = $body["data"];if ($payment["status"] === "succeeded" && $payment["amount"] === $expectedAmount && $payment["currency"] === "NGN") {// Give value once: record $payment["id"] so a second check does nothing.echo "paid: {$payment['id']}\n";} elseif (in_array($payment["status"], ["pending", "processing"], true)) {// Not failed. Wait for the webhook, or check again in a minute.echo "not yet: {$payment['status']}\n";} else {echo "not paid: {$payment['status']}\n";}verify.py # Check a payment before you give value.# Run: NABLR_SECRET_KEY=sk_test_… python3 verify.py order_1042import jsonimport osimport sysimport urllib.errorimport urllib.parseimport urllib.requestreference = sys.argv[1] if len(sys.argv) > 1 else "order_1042"expected_amount = 500000 # what your order says, in koborequest = urllib.request.Request("https://pwn-api.142-93-42-70.sslip.io/v1/payments/" + urllib.parse.quote(reference, safe=""),headers={"Authorization": f"Bearer {os.environ['NABLR_SECRET_KEY']}"},)try:with urllib.request.urlopen(request, timeout=15) as response:payment = json.load(response)["data"]except urllib.error.HTTPError as e:error = json.load(e)["error"]sys.exit(f"{e.code} {error['code']}: {error['message']}")if payment["status"] == "succeeded" and payment["amount"] == expected_amount and payment["currency"] == "NGN":# Give value once: record payment["id"] so a second check does nothing.print(f"paid: {payment['id']}")elif payment["status"] in ("pending", "processing"):# Not failed. Wait for the webhook, or check again in a minute.print(f"not yet: {payment['status']}")else:print(f"not paid: {payment['status']}")The payment is now
succeeded, with thechannelthe customer used and the card’sauthorization(brand and last four digits only).