curl --request POST \
--url https://staging.api.payluk.ng/v1/payment/verify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'customer-id: <customer-id>' \
--data '
{
"reference": "99999533334",
"otp": "123456"
}
'import requests
url = "https://staging.api.payluk.ng/v1/payment/verify"
payload = {
"reference": "99999533334",
"otp": "123456"
}
headers = {
"customer-id": "<customer-id>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'customer-id': '<customer-id>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({reference: '99999533334', otp: '123456'})
};
fetch('https://staging.api.payluk.ng/v1/payment/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://staging.api.payluk.ng/v1/payment/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reference' => '99999533334',
'otp' => '123456'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"customer-id: <customer-id>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://staging.api.payluk.ng/v1/payment/verify"
payload := strings.NewReader("{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("customer-id", "<customer-id>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://staging.api.payluk.ng/v1/payment/verify")
.header("customer-id", "<customer-id>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://staging.api.payluk.ng/v1/payment/verify")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["customer-id"] = '<customer-id>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Payment intent verified successfully",
"data": {
"id": "6a3cea83734e96016b227460",
"amount": 1000,
"reference": "t53gtryhtyut",
"fee": 0,
"transactionType": "wallet_transfer",
"currency": "NGN",
"transferDetails": null,
"cardId": null,
"walletDetails": {
"phone": "09022334422",
"name": "King David",
"narration": "test"
},
"blockchainDetails": null,
"withdrawalDetails": null,
"escrowDetails": null,
"metadata": null,
"status": "success",
"creditType": "debit",
"createdAt": "2026-06-25T08:44:51.817Z",
"updatedAt": "2026-06-25T08:46:39.479Z"
}
}{
"status": 400,
"message": "amount is required",
"data": {}
}{
"status": 429,
"message": "Too many request"
}Verify payment
Submits a previously created payment intent for processing. Payment uses a deliberate two-step flow for security: Create payment intent only stages the transaction (no money moves), and this endpoint is what actually executes it: debiting the wallet and carrying out the withdrawal, crypto transfer, card charge, or wallet transfer.
Pass the single reference returned when the intent was created. If the transaction requires confirmation (e.g. an SMS OTP / transaction PIN), include it as otp. Requires the customer-id header.
curl --request POST \
--url https://staging.api.payluk.ng/v1/payment/verify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'customer-id: <customer-id>' \
--data '
{
"reference": "99999533334",
"otp": "123456"
}
'import requests
url = "https://staging.api.payluk.ng/v1/payment/verify"
payload = {
"reference": "99999533334",
"otp": "123456"
}
headers = {
"customer-id": "<customer-id>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'customer-id': '<customer-id>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({reference: '99999533334', otp: '123456'})
};
fetch('https://staging.api.payluk.ng/v1/payment/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://staging.api.payluk.ng/v1/payment/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reference' => '99999533334',
'otp' => '123456'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"customer-id: <customer-id>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://staging.api.payluk.ng/v1/payment/verify"
payload := strings.NewReader("{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("customer-id", "<customer-id>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://staging.api.payluk.ng/v1/payment/verify")
.header("customer-id", "<customer-id>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://staging.api.payluk.ng/v1/payment/verify")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["customer-id"] = '<customer-id>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reference\": \"99999533334\",\n \"otp\": \"123456\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Payment intent verified successfully",
"data": {
"id": "6a3cea83734e96016b227460",
"amount": 1000,
"reference": "t53gtryhtyut",
"fee": 0,
"transactionType": "wallet_transfer",
"currency": "NGN",
"transferDetails": null,
"cardId": null,
"walletDetails": {
"phone": "09022334422",
"name": "King David",
"narration": "test"
},
"blockchainDetails": null,
"withdrawalDetails": null,
"escrowDetails": null,
"metadata": null,
"status": "success",
"creditType": "debit",
"createdAt": "2026-06-25T08:44:51.817Z",
"updatedAt": "2026-06-25T08:46:39.479Z"
}
}{
"status": 400,
"message": "amount is required",
"data": {}
}{
"status": 429,
"message": "Too many request"
}Authorizations
Your secret key as a Bearer token. The key prefix selects the environment: sk_test_... (staging) or sk_live_... (production); a key on the wrong host is refused with 403 Unauthorized Access. Each key is limited to 10 requests per minute (429 beyond that) and, on production, to the IP addresses on your dashboard allowlist when one is configured.
Headers
The merchant customer this request acts on behalf of.
Body
Response
Payment intent submitted and processed.