curl --request POST \
--url https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'customer-id: <customer-id>' \
--form 'message=Item not as described' \
--form file='@example-file'import requests
url = "https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = { "message": "Item not as described" }
headers = {
"customer-id": "<customer-id>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('message', 'Item not as described');
form.append('file', '<string>');
const options = {
method: 'POST',
headers: {'customer-id': '<customer-id>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}', 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/escrow/submit-dispute/{paymentToken}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data",
"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/escrow/submit-dispute/{paymentToken}"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("customer-id", "<customer-id>")
req.Header.Add("Authorization", "Bearer <token>")
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/escrow/submit-dispute/{paymentToken}")
.header("customer-id", "<customer-id>")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}")
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.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Escrow dispute submitted successfully",
"data": {
"id": "6948fb43cb570eeef76285e3",
"amount": 1000,
"purpose": "iPhone 13 Pro Max",
"description": "Black Titanium, White Titanium, Natural Titanium, Desert Titanium\n\nTitanium design\nLatest-generation Ceramic Shield front\nTextured matt glass back",
"whoPays": "buyer",
"imageUrl": [
"https://mediacloud.me/media/bWVkaWEvaW1hZ2VzL29wdGltaXplL3Z0VjhwN0psSzlIMzhEVWxicWpmV1JZdUpuSUZNWlhEdU03UkVmeHEuanBn.jpg"
],
"fee": 15,
"paymentToken": "PY_KrWLqPd90314",
"sellerId": "6a19b57ffa797d3af379ca2a",
"buyerId": "69fe44bbc2cd478224ced16f",
"paidAt": "1766391073",
"status": "DISPUTED",
"state": "OPENED",
"logs": [
"DISPUTED"
],
"channel": "API",
"isSeller": false,
"dispute": [
{
"userId": "69fe44bbc2cd478224ced16f",
"message": "Item not as described; the seal was broken on arrival",
"proofUrl": "https://mediacloud.me/media/dispute-evidence.jpg",
"createdAt": "1766394820",
"name": "Ezumah",
"profile": null,
"type": "buyer"
}
],
"category": null,
"completedAt": null,
"approvedClaimBy": null,
"refundedBy": null,
"paymentId": "6948fd20cb570eeef76286a4",
"maxDelivery": 20,
"deliveryDetails": null,
"deliveryTimeline": "minutes",
"totalQuantity": 1,
"settlementType": "STANDARD",
"milestones": [],
"createdAt": "2025-12-22T08:03:15.124Z",
"updatedAt": "2025-12-22T08:14:49.601Z",
"environment": "live",
"isMerchant": true,
"merchantId": null
}
}Submit dispute
Open or reply to a dispute. The escrow must be OPENED. Only the buyer can open a dispute; the seller can only respond once the buyer has raised one, after which the merchant resolves it. Each side may submit once.
Standard escrows are always disputable while open. A milestone escrow is disputable over the milestones the buyer has not yet confirmed; once every milestone is released the call fails with All milestones have been released; there are no held funds to dispute. Vault escrows cannot be disputed. Requires the customer-id header. Sent as multipart/form-data so evidence can be attached.
curl --request POST \
--url https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'customer-id: <customer-id>' \
--form 'message=Item not as described' \
--form file='@example-file'import requests
url = "https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = { "message": "Item not as described" }
headers = {
"customer-id": "<customer-id>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('message', 'Item not as described');
form.append('file', '<string>');
const options = {
method: 'POST',
headers: {'customer-id': '<customer-id>', Authorization: 'Bearer <token>'}
};
options.body = form;
fetch('https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}', 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/escrow/submit-dispute/{paymentToken}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data",
"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/escrow/submit-dispute/{paymentToken}"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("customer-id", "<customer-id>")
req.Header.Add("Authorization", "Bearer <token>")
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/escrow/submit-dispute/{paymentToken}")
.header("customer-id", "<customer-id>")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://staging.api.payluk.ng/v1/escrow/submit-dispute/{paymentToken}")
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.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\nItem not as described\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Escrow dispute submitted successfully",
"data": {
"id": "6948fb43cb570eeef76285e3",
"amount": 1000,
"purpose": "iPhone 13 Pro Max",
"description": "Black Titanium, White Titanium, Natural Titanium, Desert Titanium\n\nTitanium design\nLatest-generation Ceramic Shield front\nTextured matt glass back",
"whoPays": "buyer",
"imageUrl": [
"https://mediacloud.me/media/bWVkaWEvaW1hZ2VzL29wdGltaXplL3Z0VjhwN0psSzlIMzhEVWxicWpmV1JZdUpuSUZNWlhEdU03UkVmeHEuanBn.jpg"
],
"fee": 15,
"paymentToken": "PY_KrWLqPd90314",
"sellerId": "6a19b57ffa797d3af379ca2a",
"buyerId": "69fe44bbc2cd478224ced16f",
"paidAt": "1766391073",
"status": "DISPUTED",
"state": "OPENED",
"logs": [
"DISPUTED"
],
"channel": "API",
"isSeller": false,
"dispute": [
{
"userId": "69fe44bbc2cd478224ced16f",
"message": "Item not as described; the seal was broken on arrival",
"proofUrl": "https://mediacloud.me/media/dispute-evidence.jpg",
"createdAt": "1766394820",
"name": "Ezumah",
"profile": null,
"type": "buyer"
}
],
"category": null,
"completedAt": null,
"approvedClaimBy": null,
"refundedBy": null,
"paymentId": "6948fd20cb570eeef76286a4",
"maxDelivery": 20,
"deliveryDetails": null,
"deliveryTimeline": "minutes",
"totalQuantity": 1,
"settlementType": "STANDARD",
"milestones": [],
"createdAt": "2025-12-22T08:03:15.124Z",
"updatedAt": "2025-12-22T08:14:49.601Z",
"environment": "live",
"isMerchant": true,
"merchantId": null
}
}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.
Path Parameters
The escrow's payment token (e.g. PY_8AB12C9D3045).