Overview · Authentication & Requests · Test & Live Mode · Start a Payment · Payment Result · Confirm a Payment · Create an Escrow · Escrow Checkout · Create an Invoice · Errors · Security · Currencies
The DroppCash API lets your website or app accept payments, create escrows and send invoices. Everything below is a plain HTTPS request, so you can use any language.
How a payment works
POST /payment/process with the amount, currency and your own order reference. You get back a url.url. The buyer signs in with their DroppCash account and confirms the payment.web_hook and redirects the buyer to your success_url.GET /payment/check-validity to confirm the payment is real, then delivers the goods.1. Get your API access key. Sign up or log in, then open the API key page in your user dashboard. Use the key only on your server and never put it in a web page or mobile app.
2. Send it as a Bearer token in the Authorization header of every request:
Authorization: Bearer YOUR_ACCESS_KEY
Accept: application/json
Content-Type: application/json
3. Send your data as JSON (recommended) or as normal form fields. Both work.
| Item | Value |
|---|---|
| Base URL | https://droppcash.app |
| Start a payment | POST /payment/process |
| Confirm a payment | GET /payment/check-validity |
| Create an escrow | POST /payment/make-escrow |
| Create an invoice | POST /payment/create-invoice |
code field in the response body. The HTTP status is 200 even when the request fails. A success has "code": 200; anything else is an error (see Errors).In your user dashboard you can set the API service mode to test or active (live).
In both modes the buyer signs in with a DroppCash account during checkout, so create a second test account to play the buyer. In live mode that account also needs a wallet in the payment currency with enough balance.
POST https://droppcash.app/payment/process
| Param | Type | Description |
|---|---|---|
currency_code | string, max 4 | Currency to charge, e.g. USD, NGN, BTC. Must be one of the supported currencies. Required |
amount | number | Amount to charge, greater than 0, with a decimal point (e.g. 0.02). Crypto currencies allow up to 8 decimal places; other currencies use 2. Required |
custom | string, max 20 | Your own order or customer reference. It is returned to you after payment so you can match it to your order. Required |
details | string, max 255 | What the buyer is paying for. Required |
customer_email | email, max 30 | Buyer email address. Required |
web_hook | URL | Your server URL that receives the payment result (server to server). Required |
success_url | URL | Where the buyer is sent after paying. Required |
cancel_url | URL | Where the buyer is sent if they cancel. Required |
The access key is not a body parameter. Send it in the Authorization: Bearer header.
cURL
curl -X POST https://droppcash.app/payment/process \
-H "Authorization: Bearer YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{
"custom": "ORDER-1001",
"currency_code": "USD",
"amount": 280.00,
"details": "Digital Product",
"web_hook": "https://yoursite.com/web_hook.php",
"success_url": "https://yoursite.com/success",
"cancel_url": "https://yoursite.com/cancel",
"customer_email": "customer@mail.com"
}'
PHP
<?php
$parameters = [
'custom' => 'ORDER-1001',
'currency_code' => 'USD',
'amount' => 280.00,
'details' => 'Digital Product',
'web_hook' => 'https://yoursite.com/web_hook.php',
'success_url' => 'https://yoursite.com/success',
'cancel_url' => 'https://yoursite.com/cancel',
'customer_email' => 'customer@mail.com',
];
$ch = curl_init('https://droppcash.app/payment/process');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Content-Type: application/json',
'Authorization: Bearer YOUR_ACCESS_KEY',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($parameters));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($response['code'] ?? 0) === 200) {
header('Location: ' . $response['url']); // send the buyer to checkout
exit;
}
echo 'Error: ' . json_encode($response['message'] ?? 'Unknown error');
Node.js
// Node.js 18+ (run this on your server, never in the browser)
const res = await fetch('https://droppcash.app/payment/process', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
custom: 'ORDER-1001',
currency_code: 'USD',
amount: 280.0,
details: 'Digital Product',
web_hook: 'https://yoursite.com/web_hook',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
customer_email: 'customer@mail.com',
}),
});
const data = await res.json();
if (data.code === 200) {
// redirect the buyer to data.url
} else {
console.error(data.message);
}
Python
import requests
r = requests.post(
"https://droppcash.app/payment/process",
headers={"Authorization": "Bearer YOUR_ACCESS_KEY"},
json={
"custom": "ORDER-1001",
"currency_code": "USD",
"amount": 280.00,
"details": "Digital Product",
"web_hook": "https://yoursite.com/web_hook",
"success_url": "https://yoursite.com/success",
"cancel_url": "https://yoursite.com/cancel",
"customer_email": "customer@mail.com",
},
timeout=30,
)
data = r.json()
if data["code"] == 200:
checkout_url = data["url"] # redirect the buyer here
else:
print(data["message"])
Success response
{
"code": 200,
"status": "OK",
"payment_id": "AIYmQIOAz0GlmsjfhgiOeu304",
"message": "Your payment has been processed. Please follow the URL to complete the payment.",
"url": "https://droppcash.app/process-checkout?payment_id=AIYmQIOAz0GlmsjfhgiOeu304"
}
Save the payment_id with your order and redirect the buyer to url.
When the buyer pays, DroppCash does two things with the same data:
POST to your web_hook URL. Respond with any 2xx status. The reply is not read and the call is not retried.success_url with the same fields added as a query string (?code=200&status=OK&payment_id=...).| Field | Description |
|---|---|
code, status | 200 and OK for a completed payment |
payment_id | The ID returned when you started the payment |
transaction | The DroppCash transaction reference |
amount | Amount paid (up to 3 decimal places) |
charge | The fee applied to this payment |
currency | Currency code |
custom | Your reference from the request |
date | Payment date, DD-MM-YYYY |
PHP webhook example
<?php
// web_hook.php - receives the payment result from DroppCash
$paymentId = $_POST['payment_id'] ?? '';
$custom = $_POST['custom'] ?? '';
// 1. Never trust this request on its own. Ask DroppCash if the payment is real
// (see "Confirm a Payment" below).
// 2. Match $custom and the amount with YOUR order record.
// 3. Make sure you only fulfil each order once - the webhook can arrive
// together with the buyer's redirect.
http_response_code(200);
Node.js webhook example
// Node.js (Express) webhook example
app.use(express.urlencoded({ extended: false }));
app.post('/web_hook', async (req, res) => {
const { payment_id, custom } = req.body;
// 1. confirm with check-validity (next section)
// 2. match custom and amount with your own order
// 3. fulfil the order once only
res.sendStatus(200);
});
success_url.GET https://droppcash.app/payment/check-validity?payment_id=PAYMENT_ID
Send the payment_id as a query parameter and your access key in the Authorization header. This must be a GET request.
cURL
curl -G https://droppcash.app/payment/check-validity \
-H "Authorization: Bearer YOUR_ACCESS_KEY" \
--data-urlencode "payment_id=AIYmQIOAz0GlmsjfhgiOeu304"
PHP
<?php
$paymentId = 'AIYmQIOAz0GlmsjfhgiOeu304';
$ch = curl_init('https://droppcash.app/payment/check-validity?payment_id=' . urlencode($paymentId));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Authorization: Bearer YOUR_ACCESS_KEY',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($response['code'] ?? 0) === 200) {
// payment is completed - safe to deliver the order
}
Node.js
const url = new URL('https://droppcash.app/payment/check-validity');
url.searchParams.set('payment_id', 'AIYmQIOAz0GlmsjfhgiOeu304');
const res = await fetch(url, {
headers: { 'Authorization': 'Bearer YOUR_ACCESS_KEY', 'Accept': 'application/json' },
});
const data = await res.json();
if (data.code === 200) {
// payment is completed - safe to deliver the order
}
Python
import requests
r = requests.get(
"https://droppcash.app/payment/check-validity",
params={"payment_id": "AIYmQIOAz0GlmsjfhgiOeu304"},
headers={"Authorization": "Bearer YOUR_ACCESS_KEY"},
timeout=30,
)
if r.json()["code"] == 200:
pass # payment is completed - safe to deliver the order
Responses
// Payment completed
{ "code": 200, "status": "OK", "message": "Transaction is valid" }
// Payment not found or not completed yet
{ "code": 404, "status": "error", "message": "Transaction Not Found." }
// Wrong or missing access key
{ "code": 401, "status": "error", "message": "Invalid API credentials." }
custom reference and the amount from the webhook with your own order record.Escrow holds money safely until it is released to the receiver. It suits marketplaces. The money comes from the wallet of the account that owns the access key, in the currency you choose, and is taken immediately.
POST https://droppcash.app/payment/make-escrow
| Param | Type | Description |
|---|---|---|
receiver_email | Email of the person who will receive the escrow. They must already have a DroppCash account. Required | |
currency_code | string | Currency of the escrow, e.g. NGN. You need a wallet in this currency. Required |
amount | number | Amount to hold, greater than 0. Required |
description | string | What the escrow is for. Required |
pay_charge | boolean | true: you pay the escrow charge on top of the amount. false: the charge is taken from the amount when it is released. Required |
cURL
curl -X POST https://droppcash.app/payment/make-escrow \
-H "Authorization: Bearer YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{
"receiver_email": "seller@mail.com",
"currency_code": "NGN",
"amount": 20,
"description": "Digital Product",
"pay_charge": true
}'
PHP
<?php
$parameters = [
'receiver_email' => 'seller@mail.com',
'currency_code' => 'NGN',
'amount' => 20,
'description' => 'Digital Product',
'pay_charge' => true,
];
$ch = curl_init('https://droppcash.app/payment/make-escrow');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Content-Type: application/json',
'Authorization: Bearer YOUR_ACCESS_KEY',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($parameters));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
Responses
// Success
{ "code": 200, "status": "OK", "message": "An Escrow Has Been Made to seller@mail.com" }
// Validation problem (message is a list)
{ "code": 422, "status": "error", "message": ["The selected receiver email is invalid."] }
// Not enough money in your wallet
{ "code": 422, "status": "error", "message": "Insufficent Balance In Your Wallet" }
Create an invoice for your customer. DroppCash emails them a payment link and also returns it to you so you can redirect them straight to a clean checkout page.
POST https://droppcash.app/payment/create-invoice
| Param | Type | Description |
|---|---|---|
invoice_to | string | Name of the person being invoiced. Required |
email | Customer email. The customer must already have a DroppCash account. Required | |
currency_code | string | Currency the invoice is paid in, e.g. NGN. Required |
item | array of strings | Names of the items. Each name must be unique. Required |
amount | array of numbers | Price of each item, in the same order as item. Both arrays must be the same length and every amount greater than 0. The invoice total is the sum. Required |
address | string | Customer address. Optional |
invoice_status | string | publish makes the invoice payable, cancel cancels it. If left out the invoice is saved as a draft. Optional |
allow_escrow | boolean | true lets the customer pay through escrow. Optional |
pay_charge | boolean | Only matters when the customer pays through escrow: true means the escrow charge is paid on top of the invoice total. Optional |
success_url | URL | Where to send the customer after paying. Optional |
cancel_url | URL | Where to send the customer if they cancel. Optional |
cURL
curl -X POST https://droppcash.app/payment/create-invoice \
-H "Authorization: Bearer YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{
"invoice_to": "Test User",
"email": "customer@mail.com",
"currency_code": "NGN",
"item": ["Baby doll", "Toy car", "Gift wrap"],
"amount": [10, 20, 30],
"address": "Lagos, Nigeria",
"allow_escrow": true,
"pay_charge": true,
"invoice_status": "publish",
"success_url": "https://yoursite.com/success",
"cancel_url": "https://yoursite.com/cancel"
}'
PHP
<?php
$parameters = [
'invoice_to' => 'Test User',
'email' => 'customer@mail.com',
'currency_code' => 'NGN',
'item' => ['Baby doll', 'Toy car', 'Gift wrap'],
'amount' => [10, 20, 30],
'address' => 'Lagos, Nigeria',
'allow_escrow' => true,
'pay_charge' => true,
'invoice_status' => 'publish',
'success_url' => 'https://yoursite.com/success',
'cancel_url' => 'https://yoursite.com/cancel',
];
$ch = curl_init('https://droppcash.app/payment/create-invoice');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Content-Type: application/json',
'Authorization: Bearer YOUR_ACCESS_KEY',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($parameters));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($response['code'] ?? 0) === 200) {
header('Location: ' . $response['url']); // customer pays on the invoice page
exit;
}
Responses
// Success
{
"code": 200,
"status": "OK",
"message": "Invoice Has Been Made",
"url": "https://droppcash.app/view-invoice/eyJpdiI6..."
}
// Customer email has no account (a link to register is returned)
{ "code": 500, "status": "OK", "message": "User Not Registerred On Dropppay", "url": "https://droppcash.app/user/register" }
// item and amount lists do not match
{ "code": 500, "status": "error", "message": "An Error In Your API Parameters Caused This." }
With escrow checkout the buyer pays into DroppCash escrow instead of paying you directly. The money is held safely until the buyer releases it to you. You are the seller: the account that owns the API access key is the one that gets paid.
POST /payment/escrow/process and gets a checkout url.success_url and calls your web_hook with event escrow.funded. You can now ship the goods or deliver the service.escrow.released and the money is in your wallet.| Endpoint | What it does |
|---|---|
POST /payment/escrow/process | Start an escrow payment and get the buyer checkout URL |
GET /payment/escrow/status | Check the current state of an escrow payment |
POST /payment/escrow/refund | Return the held money to the buyer (while it is still held) |
POST /payment/escrow/simulate | Test mode only: pretend to be the buyer (release or dispute) |
Start an escrow payment takes the same fields as Start a Payment (currency_code, amount, custom, details, customer_email, web_hook, success_url, cancel_url) plus one optional field:
| Param | Type | Description |
|---|---|---|
pay_charge | boolean | true: the buyer pays the escrow fee on top of the amount. false (default): the fee is taken from the amount when the money is released to you. Optional |
cURL
curl -X POST https://droppcash.app/payment/escrow/process \
-H "Authorization: Bearer YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency_code": "NGN",
"amount": 7500.50,
"custom": "ORDER-1001",
"details": "Laptop bag",
"customer_email": "buyer@mail.com",
"web_hook": "https://yoursite.com/webhook.php",
"success_url": "https://yoursite.com/success.php",
"cancel_url": "https://yoursite.com/cancel.php",
"pay_charge": false
}'
{
"code": 200,
"status": "OK",
"payment_id": "AIYmQIOAz0GlmsjfhgiOeu304",
"message": "Your escrow payment has been created. Please send the buyer to the URL to fund the escrow.",
"url": "https://droppcash.app/payment/process-checkout?payment_id=AIYmQIOAz0GlmsjfhgiOeu304"
}
Check the status with GET /payment/escrow/status?payment_id=... (Bearer key). Always use this call, never the address bar, to confirm what happened.
{
"code": 200,
"status": "OK",
"escrow": {
"payment_id": "AIYmQIOAz0GlmsjfhgiOeu304",
"custom": "ORDER-1001",
"amount": "7500.5",
"currency": "NGN",
"test": 0,
"escrow_id": "42",
"escrow_status": "held",
"transaction": "K3JD8SL2QW1X",
"charge": "75",
"funded_at": "2026-10-16T10:15:00+00:00"
}
}
| escrow_status | Meaning |
|---|---|
awaiting_payment | The buyer has not funded the escrow yet |
held | Funded: the money is held. You can ship or deliver. |
released | The buyer released the money to you |
disputed | A dispute is open. DroppCash support decides who gets the money. |
refunded | The money went back to the buyer |
released_by_admin | DroppCash support decided in your favour and paid you |
closed | The escrow was closed with no payout |
Refund the buyer (only while the status is held): POST /payment/escrow/refund with {"payment_id": "..."}. The buyer gets back everything they paid: the held amount, plus the escrow fee if the buyer paid it (pay_charge true). The response shows the refunded total.
{ "code": 200, "status": "OK", "message": "Escrow refunded to the buyer.", "refunded": "7575.5" }
// not refundable (already released, disputed or refunded)
{ "code": 422, "status": "error", "message": "Escrow can only be refunded while it is held." }
Webhook events. DroppCash sends a form-encoded POST to your web_hook URL every time something happens. Events: escrow.funded, escrow.released, escrow.refunded, escrow.disputed, escrow.closed. Answer with any 2xx status. Calls are not retried, so also use the status call as a safety net.
| Field | Description |
|---|---|
event | One of the events above |
status | The escrow_status after the event |
payment_id, escrow_id | Identify the payment and the escrow |
transaction | DroppCash transaction reference |
amount, currency, custom | Amount, currency and your own reference from the request |
test | 1 for a test-mode event, 0 for live |
date | Time of the event (ISO 8601) |
Every webhook is signed. The header X-DroppCash-Signature holds sha256= followed by the HMAC-SHA256 of the raw request body, using your API access key as the secret. Reject any call whose signature does not match, then also check that amount and currency match your order.
PHP
<?php
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DROPPCASH_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, 'YOUR_ACCESS_KEY');
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
parse_str($raw, $event); // $event['event'], $event['payment_id'], ...
Node.js
// Node.js (Express): use express.raw({ type: '*/*' }) so you get the raw body
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', 'YOUR_ACCESS_KEY').update(req.body).digest('hex');
const given = req.get('X-DroppCash-Signature') || '';
const ok = expected.length === given.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
if (!ok) return res.sendStatus(401);
const event = Object.fromEntries(new URLSearchParams(req.body.toString()));
res.sendStatus(200);
});
Python
import hmac, hashlib
from urllib.parse import parse_qs
def is_genuine(raw_body: bytes, signature_header: str, access_key: str) -> bool:
expected = "sha256=" + hmac.new(access_key.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
# event = {k: v[0] for k, v in parse_qs(raw_body.decode()).items()}
Test mode. With a test-mode key nothing is charged and no real escrow exists, but the whole flow works: checkout, redirect, webhooks, status and refund. Because no real buyer is there to release the money, use POST /payment/escrow/simulate with {"payment_id": "...", "action": "released"} (or "disputed") to act as the buyer. This call is refused for live payments.
409 and tells you to use /payment/escrow/status.Remember: the HTTP status is always 200. Check the code field in the JSON body.
| code | Meaning | What to do |
|---|---|---|
200 | Success | Continue. |
401 | Invalid API credentials. | The access key is missing or wrong. Check the Authorization: Bearer header. |
404 | Requested currency not found. or Transaction Not Found. | Check currency_code against the supported list, or the payment_id. A payment that is not completed yet also returns 404 on check-validity. For escrow, You do not have a wallet in this currency. means the account that owns the key needs a wallet in that currency. |
403 / 409 | Not allowed for this payment (for example simulate on a live payment, or check-validity on an escrow payment) | Use the call that matches the payment type, as the message says. |
422 | Validation failed, or Insufficent Balance In Your Wallet (escrow) | For validation, message is a list of what to fix. For balance, top up your wallet. |
500 | Server problem, or invoice parameters/customer account problem | Check the invoice item and amount lists and that the customer is registered. If it keeps happening, contact support. |
Example validation error:
{
"code": 422,
"status": "error",
"message": [
"The amount must be greater than 0.",
"The success url must be a valid URL."
]
}
custom and the amount with your own order.payment_id against the order and ignore repeats.web_hook, success_url and cancel_url.CURLOPT_SSL_VERIFYHOST or verify=False) in your own code.Use the currency code exactly as shown. The list updates automatically when new currencies are added.
| Currency Name | Currency Symbol | Currency Code |
|---|---|---|
| US Dollar | $ | USD |
| European Euro | € | EUR |
| British Pound | £ | GBP |
| Bangladeshi Taka | ৳ | BDT |
| Bitcoin | BTC | BTC |
| Indian Rupee | ₹ | INR |
| Japanese Yen | ¥ | JPY |
| Nigerian naira | N | NGN |
| Chinese Yuan | CN¥ | CNY |
| South African Rand | R | ZAR |
| Kenyan Shilling | Ksh | KES |
| Emirati Dirham | د.إ | AED |
| Ghanaian Cedi | GH₵ | GHS |
| Canadian Dollar | Can$ | CAD |
| Egyptian Pound | E£ | EGP |
| Rwandan franc | R₣ | RWF |
| Singapore Dollar | S$ | SGD |
| Ugandan Shilling | USh | UGX |
| Turkish Lira | ₺ | TRY |
| Australian Dollar | A$ | AUD |
| Malaysian Ringgit | RM | MYR |
| Romanian leu | lei | RON |
| New Zealand Dollar | NZD | NZD |
| Hungarian forint | Ft | HUF |
| Mexican Peso | Mex$ | MXN |
| Hong Kong Dollar | HK$ | HKD |
| Brazilian Real | R$ | BRL |
| Swiss Franc | CHf | CHF |
| Bulgarian Lev | Лв. | BGN |
| Czech Koruna | Kč | CZK |
| Danish Krone | Kr. | DKK |
| Norwegian Krone | kr | NOK |
| Thai Baht | ฿ | THB |
| Ukrainian hryvnia | ₴ | UAH |
| Polish Zloty | zł | PLN |
| Swedish Krona | skr | SEK |
| Dogecoin | Ɖ | DOGE |
| Litecoin | Ł | LTC |
| Kucoin Token | KCS | KCS |
| Solana | SOL | SOL |
| Binance Coin | BNB | BNB |
| USDT | ₮ | USDT |
| Ethereum | ETH | ETH |
| ADA | ₳ | ADA |
| TRX | TRX | TRX |
| Israeli Shekel | ₪ | ILS |