Payuee Commerce Infrastructure API
Enterprise-grade commerce infrastructure powering escrow-secured transactions, vendor-managed supply, and integrated logistics — all accessible through a single API.
Fund wallets, access products, create orders, verify delivery, and automate payouts with full trust and control.
What is Payuee?
Payuee is a fully managed commerce ecosystem built for enterprise integrations. Vendors, products, logistics, and payments are all controlled and orchestrated by Payuee, allowing partners to integrate once and operate without managing supply or fulfillment.
At its core, Payuee enforces trust through escrow — holding funds securely and releasing payments only after verified delivery — ensuring every transaction is completed with full accountability.
Escrow-Protected Transactions
Payuee locks and governs all funds, releasing payments only after successful delivery verification — no party can access or redirect funds outside Payuee’s control.
End-to-End Fraud Protection
Multi-layer verification using QR codes/pin, API callbacks, and delivery rules ensures every transaction is validated before settlement.
Automated Settlement Engine
Payuee calculates and distributes payouts instantly upon verification — vendors/logistics are paid, partners earn commissions, and the platform retains its share.
Intelligent Refund System
Automatic refunds trigger on delivery failure or timeout — keeping your platform fair and dispute-free without manual intervention.
Architecture Overview
Payuee is the central commerce orchestration layer powering all transactions between partner platforms, Payuee-controlled vendors, and integrated delivery systems. Rather than acting as a passive intermediary, Payuee enforces transaction flow, delivery conditions, and payout rules across the entire lifecycle.
At the core is the escrow engine, which governs fund locking, delivery verification, and automated settlement from wallet funding through to final payout.
Core Integration Flow
Quick Start
Get up and running with Payuee in minutes. Fund a wallet, create your first order, and complete a full escrow-secured transaction from start to settlement.
Step 1: Get Your API Key
Sign in to the Payuee Dashboard and navigate to → API Keys. Create a new key and store it securely.
# Set variables
PUBLIC_KEY="payuee_pk_live_xxxxx"
SECRET_KEY="payuee_sk_live_xxxxx"
TIMESTAMP=$(date +%s)
METHOD="GET"
PATH="/v1/auth-status"
BODY=""
# Generate HMAC SHA256 signature
SIGNATURE=$(echo -n "${TIMESTAMP}${METHOD}${PATH}${BODY}" | \
openssl dgst -sha256 -hmac "${SECRET_KEY}" | sed 's/^.* //')
# Make request
curl https://escrow.payuee.com/v1/auth-status \
-X GET \
-H "Authorization: Bearer $SECRET_KEY" \
-H "X-Payuee-Public-Key: $PUBLIC_KEY" \
-H "X-Payuee-Timestamp: $TIMESTAMP" \
-H "X-Payuee-Signature: $SIGNATURE" \
-H "X-Payuee-Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json"
{
"success": "company authenticated",
}
Authentication
All API requests to Payuee must be authenticated and securely signed. Each request includes your API keys along with a generated HMAC SHA256 signature.
Authorization: Bearer payuee_sk_your_secret_key_here
X-Payuee-Public-Key: payuee_pk_your_public_key_here
X-Payuee-Timestamp: UNIX_TIMESTAMP
X-Payuee-Signature: GENERATED_SIGNATURE
X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID
| Key Type | Prefix | Usage |
|---|---|---|
Public Key | payuee_pk_ | Included in all requests for identification. |
Secret Key | payuee_sk_ | Used for request signing and authorization |
Each request must also include a valid signature generated using your Secret Key. See the Authentication Check example above for a complete working implementation.
Environments
- Use low-value transactions during development
- Start with authentication and product retrieval endpoints
- Gradually test order creation and delivery flows
- Live transactions with real funds
- Full escrow enforcement
- Integrated vendor and delivery orchestration
- 99.99% uptime SLA
- 24/7 support access
Wallet System
Payuee uses a prefunded wallet model. Your company maintains a wallet balance from which funds are locked into escrow when orders are processed.
All order execution is controlled by Payuee based on your wallet balance.
🔄 Order Processing Behavior
There are two possible order flows depending on your wallet state:
-
1. Funded Wallet (Normal Flow)
If your wallet has sufficient balance, Payuee immediately locks the required amount in escrow and the order proceeds to vendor fulfillment. -
2. Insufficient Wallet (HOLD Flow)
If your wallet does not have enough funds, the order is placed on HOLD and you are notified via webhook to fund your wallet.
Orders on HOLD remain pending for up to 24 hours.
- If funded within this window → the order automatically resumes and escrow is locked
- If not funded → the order is automatically cancelled
Wallet States
| State | Description |
|---|---|
| ACTIVE | Wallet is funded and ready to process orders |
| LOW_BALANCE | Balance is low; new orders may be placed on HOLD |
| INSUFFICIENT | Order amount exceeds available balance; order is placed on HOLD |
| FROZEN | Wallet is restricted due to compliance or administrative review |
HOLD until resolved.
⚙️ Transaction Mechanics
The Escrow Engine is the financial core of Payuee. When an order is created, the corresponding funds are atomically locked — preventing double-spending and ensuring funds are available for release when delivery is confirmed.
Atomic Escrow Locking
When an order is confirmed with sufficient wallet balance, Payuee immediately locks the required funds in escrow. If the wallet cannot cover the order, it is placed on HOLD until funding is completed.
Timeout Enforcement
Each order is governed by strict timing rules. Orders on HOLD expire after a defined window (24 hours) if funding is not completed. For active escrows, funds remain locked until delivery is verified or the order lifecycle expires.
Dispute Handling
If a dispute is raised, funds remain securely held in escrow until resolution. Disputes can be initiated via API or managed through the Payuee dashboard.
Audit Trail
Every escrow state change is recorded with a timestamp and initiating actor. Full audit logs are accessible via API and dashboard for transparency and reconciliation.
Order Lifecycle
An order moves through distinct states from creation to settlement. Understanding this lifecycle is essential for handling webhooks and building a reliable integration.
| Status | Description | Next Steps |
|---|---|---|
| CREATED | Order is received and validated by Payuee | System attempts escrow lock |
| ESCROW_LOCKED | Funds are successfully locked in escrow | Vendor is notified for fulfillment |
| CONFIRMED | Vendor has accepted and is preparing the order | Await delivery |
| DELIVERED | Delivery is verified via QR scan or API confirmation | Trigger escrow release |
| RELEASED | Funds are distributed to all parties | Order completed |
| HOLD | Insufficient wallet balance | Fund wallet to resume → moves to ESCROW_LOCKED |
| FAILED | Delivery failed or order expired | Refund initiated |
| REFUNDED | Funds returned to wallet | Order closed |
| CANCELLED | Order cancelled before fulfillment or funding | Order closed |
Settlement System
Once delivery is verified, Payuee automatically distributes funds across all involved parties. Settlement is fully managed and executed without manual intervention.
Each order may involve multiple payouts:
- Vendor — receives the base product price
- Logistics — receives the delivery/shipping fee
- Platform Fee — deducted based on vendor subscription plan
- API Partner — receives a revenue share (e.g. 30%)
- Payuee — retains the remaining platform share (e.g. 70%)
Verification System
Payuee uses a unified, two-factor delivery verification system combining QR code scanning and a secure 6-digit transaction PIN. This ensures that every delivery is physically confirmed and cryptographically authorized before escrow release.
QR Code Verification
Every order generates a unique, one-time-use QR code tied to the order ID, delivery address, and escrow session. The QR code is required to confirm physical delivery.
6-Digit PIN Confirmation
After successful QR validation, the buyer must enter a secure 6-digit transaction PIN to authorize final escrow release.
API Reference
Use the following base URL to make API requests:
https://escrow.payuee.com/v1
Include the following headers in every request:
Authorization: Bearer YOUR_SECRET_KEYX-Payuee-Public-Key: Your public API keyX-Payuee-Timestamp: Current UNIX timestamp (seconds)X-Payuee-Signature: HMAC SHA256 signatureX-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID
Signature payload format:
payload = timestamp + UPPERCASE(HTTP_METHOD) + request_path + request_body
signature = HMAC_SHA256(payload, SecretKey)
Requests with missing headers, expired timestamps, or invalid signatures will be rejected with
401 Unauthorized.
The Payuee Order Engine automatically checks wallet funds when an order is placed:
- Sufficient funds: Funds are locked in escrow and order proceeds to fulfillment.
- Insufficient funds: Order is placed on HOLD for up to 24 hours.
- Funded within 24 hours: Order resumes automatically.
- Not funded: Order is cancelled and a failure webhook is sent.
When creating or cancelling orders, you must include an
X-Payuee-Idempotency-Key to prevent duplicate processing caused by retries or timeouts.
X-Payuee-X-Payuee-Idempotency-Key — Unique request identifier
If the same key is sent multiple times with the same request, Payuee will return the original response instead of reprocessing it.
/v1/products
Retrieve available products from Payuee vendors. Supports filtering by category, price range, weight, distance, tags, and sorting options. Results are paginated.
| Parameter | Type | Description |
|---|---|---|
category | string | Filter by category: outfits, jewelry, kids-accessories, cars-car-parts, tools, gadgets, others. Default: all |
user_lat | float | User latitude for location-based results |
user_lon | float | User longitude for location-based results |
max_distance | integer | Max distance (km) from vendor (default: 10) |
min_price | float | Minimum price filter |
max_price | float | Maximum price filter |
min_weight | float | Minimum weight (kg) |
max_weight | float | Maximum weight (kg) |
page_number | integer | Pagination page (default: 1) |
sort_option |
integer |
Sorting option used to control product ordering.
View available sorting options |
tags | array | Filter by product tags |
# Fetch store products
curl https://escrow.payuee.com/v1/products \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"category": "outfits",
"user_lat": 6.5244,
"user_lon": 3.3792,
"max_distance": 100,
"min_price": 0,
"max_price": 100000,
"page_number": 1,
"sort_option": 7
}'
{
"pagination": {
"NextPage": 2,
"PreviousPage": 0,
"CurrentPage": 1,
"TotalPages": 1,
"Offset": 0,
"AllRecords": 2
},
"stores": [
{
"eshop_user_id": 19,
"store_name": "Mariyahnima"
},
{
"eshop_user_id": 51,
"store_name": "Queentee"
}
],
"success": [
{
"ID": 92,
"CreatedAt": "2024-12-06T09:31:58.006Z",
"UpdatedAt": "2025-03-12T15:07:15.968Z",
"eshop_user_id": 19,
"product_url_id": "lingeries-bra--pants-tight-boxers-92",
"title": "LINGERIES (UNISEX & CHILDREN)",
"description": "Male and female underwear, bras, lounge wears, children's underwear and more.",
"net_weight": 1,
"selling_price": 3000,
"currency": "NGN",
"product_stock": 72,
"stock_remaining": 71,
"category": "outfits",
"estimated_delivery": 10,
"stock_availability_status": "in-stock",
"vendor_type": "basic",
"sales": 1,
"product_image": [
{
"url": "payuee-products/1733477516635057121_image0.jpg"
},
{
"url": "payuee-products/1733477517425657271_image1.jpg"
}
],
"clothing_sizes": "S,M,L,XL",
"featured": true,
"on_sale": true
},
{
"ID": 255,
"CreatedAt": "2025-10-28T20:11:38.647Z",
"UpdatedAt": "2025-10-28T20:11:38.957Z",
"eshop_user_id": 51,
"product_url_id": "crazy-jeans-255",
"title": "Crazy Jeans",
"description": "Premium denim jeans with bold ripped design and durable stitching.",
"net_weight": 0.5,
"selling_price": 13300,
"currency": "NGN",
"product_stock": 20,
"stock_remaining": 20,
"category": "outfits",
"estimated_delivery": 7,
"stock_availability_status": "in-stock",
"vendor_type": "basic",
"sales": 0,
"product_image": [
{
"url": "payuee-products/1761682297960121211_image0.jpg"
},
{
"url": "payuee-products/1761682298419326298_image1.jpg"
}
],
"clothing_sizes": "M,L,XXL",
"featured": false,
"on_sale": true
}
]
}
https://payuee.com/image/
Example:
https://payuee.com/image/payuee-products/xxxx.jpg
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST |
400 | Request body is malformed or contains invalid parameters |
UNAUTHORIZED |
401 | Authentication failed. Invalid API keys or signature |
FORBIDDEN |
403 | Access denied. Account may be restricted or under review |
NOT_FOUND |
404 | Requested resource could not be found |
RATE_LIMITED |
429 | Too many requests. Please retry after some time |
INTERNAL_ERROR |
500 | An unexpected error occurred on our servers |
/v1/products/search
Retrieve available products from Payuee vendors using advanced search, filtering, and pagination. Supports category filtering, distance-based discovery, price ranges, weight filtering, product tags, and customizable sorting options to help customers find the most relevant products quickly.
| Parameter | Type | Description |
|---|---|---|
search_term | string | Search term to filter products |
limit | number | Maximum number of results to return (Max 50), (Min 5) |
category | string | Filter by category: outfits, jewelry, kids-accessories, cars-car-parts, tools, gadgets, others. Default: all |
min_price | float | Minimum price filter |
max_price | float | Maximum price filter |
min_weight | float | Minimum weight (kg) |
max_weight | float | Maximum weight (kg) |
page_number | integer | Pagination page (default: 1) |
sort_option |
integer |
Sorting option used to control product ordering.
View available sorting options |
tags | array | Filter by product tags |
# Fetch store products
curl https://escrow.payuee.com/v1/products/search \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"search_term": "lingeries",
"limit": 10,
"category": "outfits",
"min_price": 0.0,
"max_price": 100000.0,
"min_weight": 0.5,
"max_weight": 100.0,
"page_number": 1,
"sort_option": 7,
"tags": ["underwear", "bras", "lounge wears", "children's underwear"]
}'
{
"success": [
{
"ID": 92,
"CreatedAt": "2024-12-06T09:31:58.006Z",
"UpdatedAt": "2025-03-12T15:07:15.968Z",
"eshop_user_id": 19,
"product_url_id": "lingeries-bra--pants-tight-boxers-92",
"title": "LINGERIES (UNISEX & CHILDREN)",
"description": "Male and female underwear, bras, lounge wears, children's underwear and more.",
"net_weight": 1,
"selling_price": 3000,
"currency": "NGN",
"product_stock": 72,
"stock_remaining": 71,
"category": "outfits",
"estimated_delivery": 10,
"stock_availability_status": "in-stock",
"vendor_type": "basic",
"sales": 1,
"product_image": [
{
"url": "payuee-products/1733477516635057121_image0.jpg"
},
{
"url": "payuee-products/1733477517425657271_image1.jpg"
}
],
"clothing_sizes": "S,M,L,XL",
"featured": true,
"on_sale": true
},
{
"ID": 255,
"CreatedAt": "2025-10-28T20:11:38.647Z",
"UpdatedAt": "2025-10-28T20:11:38.957Z",
"eshop_user_id": 51,
"product_url_id": "crazy-jeans-255",
"title": "Crazy Jeans",
"description": "Premium denim jeans with bold ripped design and durable stitching.",
"net_weight": 0.5,
"selling_price": 13300,
"currency": "NGN",
"product_stock": 20,
"stock_remaining": 20,
"category": "outfits",
"estimated_delivery": 7,
"stock_availability_status": "in-stock",
"vendor_type": "basic",
"sales": 0,
"product_image": [
{
"url": "payuee-products/1761682297960121211_image0.jpg"
},
{
"url": "payuee-products/1761682298419326298_image1.jpg"
}
],
"clothing_sizes": "M,L,XXL",
"featured": false,
"on_sale": true
}
]
}
https://payuee.com/image/
Example:
https://payuee.com/image/payuee-products/xxxx.jpg
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST |
400 | Request body is malformed or contains invalid parameters |
UNAUTHORIZED |
401 | Authentication failed. Invalid API keys or signature |
FORBIDDEN |
403 | Access denied. Account may be restricted or under review |
NOT_FOUND |
404 | Requested resource could not be found |
RATE_LIMITED |
429 | Too many requests. Please retry after some time |
INTERNAL_ERROR |
500 | An unexpected error occurred on our servers |
/v1/product/{id}
Retrieve a single product by its unique ID. Returns full product details along with a list of related products based on category and vendor context.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | The unique ID of the product |
product_url_id |
string | No | The unique URL ID of the product |
# Get product by ID
curl https://escrow.payuee.com/v1/products/90 \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
{
"success": {
"ID": 90,
"CreatedAt": "2024-12-05T03:29:01.801Z",
"UpdatedAt": "2026-04-19T04:52:13.638Z",
"DeletedAt": null,
"eshop_user_id": 17,
"product_url_id": "italian-shoe-90",
"title": "Italian shoe",
"description": "An Italian shoe",
"net_weight": 50,
"image_quality": 1,
"initial_cost": 50000,
"selling_price": 48000,
"on_discount": true,
"currency": "Naira ?",
"product_stock": 3,
"stock_remaining": 3,
"discount_type": "fixed-price",
"category": "outfits",
"tags": "[{\"value\":\"Casual Wear\"},{\"value\":\"Sportswear\"},{\"value\":\"Work Outfits\"},{\"value\":\"shoes\"},{\"value\":\"Italian\"}]",
"publish_status": "publish",
"estimated_delivery": 7,
"product_length": 0,
"product_width": 0,
"product_height": 0,
"shipping_class_selection": "Basic Shipping",
"stock_availability_status": "in-stock",
"reposts": 0,
"repost": false,
"reposted": false,
"repost_max_price": 0,
"original_product_id": null,
"original_eshop_user_id": null,
"reposted_selling_price": 0,
"sales": 0,
"can_sell": true,
"vendor_type": "basic",
"issue_report_count": 9,
"product_review_count": 0,
"product_image": [
{
"ID": 193,
"url": "payuee-products/1733369341012001773_image0.jpg",
"original_file_name": "image0.jpg"
}
],
"clothing_sizes": "",
"shoe_sizes": "30-40",
"featured": true,
"best_seller": false,
"on_sale": true,
"views": 0,
"wishlist_count": 0,
"last_sold_at": "0001-01-01T00:00:00Z"
},
"related": [
{
"ID": 92,
"CreatedAt": "2024-12-06T09:31:58.006Z",
"UpdatedAt": "2026-04-19T04:52:13.639Z",
"DeletedAt": null,
"eshop_user_id": 19,
"product_url_id": "lingeries-bra--pants-tight-boxers-92",
"title": "LINGERIES (BOTH MALE AND FEMALE, CHILDREN)",
"description": "Male and female underwear, female night wears, lounge wears, children underwear...",
"selling_price": 3000,
"currency": "Naira ?",
"product_stock": 72,
"stock_remaining": 71,
"category": "outfits",
"estimated_delivery": 10,
"stock_availability_status": "in-stock",
"vendor_type": "basic",
"product_review_count": 0,
"product_image": [
{
"url": "payuee-products/1733477516635057121_image0.jpg"
}
],
"featured": true,
"on_sale": true
}
]
}
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST |
400 | Invalid product ID (must be a valid integer) |
UNAUTHORIZED |
401 | Authentication failed or invalid API credentials |
NOT_FOUND |
404 | Product not found or does not exist |
INTERNAL_ERROR |
500 | Unexpected server error |
/v1/wallet/balance
Retrieve the current balance of your Payuee Enterprise Wallet. This wallet is used to prefund orders before they are processed through the escrow engine.
curl https://escrow.payuee.com/v1/wallet/balance \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
{
"status": "success",
"wallet_balance": 250000,
"currency": "NGN"
}
wallet_balance is returned in the smallest currency unit (kobo for NGN).
Always convert before displaying to end users.
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing authentication credentials |
INVALID_SIGNATURE |
401 | Request signature validation failed |
ACCOUNT_NOT_FOUND |
404 | Wallet account does not exist |
INTERNAL_ERROR |
500 | Unable to fetch wallet balance |
/v1/wallet/fund
Retrieve your Payuee Enterprise Wallet funding details. These include a dedicated virtual account assigned to your business. You can transfer funds directly to this account from your business bank account or initiate the transfer through your preferred payment provider or API integration. Once the transfer is confirmed, your wallet will be automatically credited.
A webhook notification will be sent to your system after successful wallet funding.
curl https://escrow.payuee.com/v1/wallet/fund \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
{
"wallet_funding_account": {
"account_name": "PAYUEE NETWORK LIMITED",
"account_number": "1385097053",
"bank_name": "Paga Bank",
"bank_code": "100002"
}
"wallet_balance": 250000,
}
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
ACCOUNT_NOT_FOUND |
404 | No funding account has been assigned to this business |
SERVICE_ERROR |
500 | Unable to retrieve funding details at this time |
/v1/location/states
Retrieve the list of all available states supported by Payuee. These states are used when creating orders to define customer delivery locations and calculate shipping accurately.
This endpoint is typically used to populate state selection fields in your checkout or onboarding flow.
curl https://escrow.payuee.com/v1/location/states \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
{
"success": true,
"states": [
"Lagos",
"Abuja",
"Rivers",
"Kano",
"Oyo"
]
}
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
SERVICE_ERROR |
500 | Unable to retrieve states at this time |
/v1/location/cities
Retrieve all cities (LGAs) and wards within a specified state. This endpoint returns a flattened, UI-ready structure including location coordinates for accurate delivery and shipping calculations.
Each entry represents a specific delivery area (ward) within a city and includes latitude and longitude for precise logistics handling.
display field is preformatted for direct use in dropdowns or address selection interfaces (e.g. "Lekki - Phase 1").
# Get cities and wards for a state
curl "https://escrow.payuee.com/v1/location/cities?state=Lagos" \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
| Query Param | Type | Required | Description |
|---|---|---|---|
state |
string | Yes | Name of the state (must match values from /location/states) |
/location/states to build a complete delivery location selector.
{
"success": true,
"lga": [
{
"state": "Lagos",
"city": "Lekki",
"ward": "Phase 1",
"latitude": 6.4474,
"longitude": 3.3903,
"display": "Lekki - Phase 1"
},
{
"state": "Lagos",
"city": "Ikeja",
"ward": "Alausa",
"latitude": 6.6059,
"longitude": 3.3491,
"display": "Ikeja - Alausa"
}
]
}
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST |
400 | The state query parameter is required |
NOT_FOUND |
404 | The specified state was not found |
UNAUTHORIZED |
401 | Invalid or missing API credentials |
/v1/order/shipping-fees
Retrieve the best available shipping fees across multiple logistics providers for each vendor in a cart. Payuee automatically evaluates vendor configurations, logistics companies, distance, weight, and order value to return the most optimal shipping option per vendor.
This endpoint should be called before creating an order to ensure accurate shipping fees and valid logistics configuration.
The returned fee, config_id, and method_id must be passed when creating the order.
# Get shipping fees
curl https://escrow.payuee.com/v1/order/shipping-fees \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"vendors": [5, 9],
"state": "Lagos",
"city": "Lekki",
"latitude": 6.4474,
"longitude": 3.3903,
"cart_items": [
{
"product_id": 12,
"eshop_user_id": 5,
"quantity": 2
}
]
}'
| Field | Type | Required | Description |
|---|---|---|---|
vendors | array | Yes | List of vendor IDs in the cart |
state | string | Yes | Delivery state |
city | string | Yes | Delivery city |
latitude | float | Yes | Buyer latitude (improves distance-based pricing) |
longitude | float | Yes | Buyer longitude |
cart_items | array | Yes | Cart items used for shipping calculation |
cart_items[].product_id | integer | Yes | Product ID |
cart_items[].eshop_user_id | integer | Yes | Vendor ID for the product (same as eshop_user_id from product data) |
cart_items[].eshop_user_id | integer | Yes | Vendor ID for the product |
cart_items[].quantity | integer | Yes | Quantity of the product |
vendors array should contain the vendor IDs for the products in your cart.
You can obtain each vendor ID from the eshop_user_id field returned in the product response.
{
"shipping": [
{
"vendor_id": 5,
"fee": 2500,
"reason": "distance_based pricing applied",
"logistics_source": "system_resolver",
"config_id": 2,
"company_name": "DHL",
"method_id": "distance_based"
}
],
"cart": [
{
"product_id": 12,
"quantity": 2
}
]
}
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
INVALID_REQUEST |
400 | Missing or invalid request parameters |
CART_ERROR |
500 | Unable to process cart items for shipping calculation |
SHIPPING_UNAVAILABLE |
404 | No valid shipping configuration found for one or more vendors |
/v1/order/create
Create a new order across one or more vendors. Products are automatically grouped by vendor, and shipping is calculated per vendor based on location, logistics configuration, and selected method. Funds are locked in escrow upon successful processing.
trans_code is a customer verification PIN. It will be required
at key stages of the order lifecycle (such as confirming delivery).
Ensure the customer securely stores or remembers this code.
Important: This code should be created and known only by the customer. Do not auto-generate without user awareness.
| Field | Type | Required | Description |
|---|---|---|---|
trans_code | string | Yes | Secure transaction code for authorizing the order |
webhook_response_url | string | Yes | URL to receive webhook responses. Takes precedence over dashboard webhook URL. Falls back if not provided. |
customer | object | Yes | Customer information and delivery details |
customer.email | string | Yes | Customer email address |
customer.first_name | string | Yes | First name |
customer.last_name | string | Yes | Last name |
customer.phone_number | string | Yes | Phone number |
customer.state | string | Yes | Delivery state |
customer.city | string | Yes | Delivery city |
customer.address_1 | string | Yes | Primary address |
customer.address_2 | string | No | Secondary address |
customer.order_note | string | No | Order notes from customer |
customer.latitude | float | Yes | Precise delivery latitude (improves shipping accuracy) |
customer.longitude | float | Yes | Precise delivery longitude |
cart_items | array | Yes | List of products to order |
cart_items[].product_id | integer | Yes | Product ID |
cart_items[].cart_meta.quantity | integer | Yes | Quantity |
cart_items[].cart_meta.outfit_size | string | No | Selected size (if applicable) |
shipping | array | Yes | Shipping selection per vendor |
shipping[].vendor_id | integer | Yes | Vendor identifier |
shipping[].fee | float | Yes | Calculated shipping fee |
shipping[].method_id | string | Yes | Shipping method (distance_based, weight_based, etc.) |
shipping[].config_id | integer | Yes | Shipping configuration ID |
shipping[].company_name | string | Yes | Logistics provider name |
# Create order
curl https://escrow.payuee.com/v1/order/create \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"trans_code": "123456",
"webhook_response_url": "https://yourdomain.com/webhook",
"customer": {
"email": "jane@email.com",
"first_name": "Jane",
"last_name": "Okafor",
"phone_number": "+2348099001122",
"state": "Lagos",
"city": "Lekki",
"latitude": 6.4474,
"longitude": 3.3903,
"address_1": "22 Adeola Odeku Street",
"address_2": "",
"order_note": "Please call when you arrive",
"zip_code": "",
"province": "",
"save_address": true
},
"cart_items": [
{
"product_id": 12,
"cart_meta": {
"quantity": 2,
"outfit_size": "M"
}
}
],
"shipping": [
{
"vendor_id": 5,
"fee": 2500,
"method_id": "distance_based",
"config_id": 2,
"company_name": "DHL"
}
]
}'
{
"success": true,
"order_ids": [
10293,
34523
],
"message": "order created successfully"
}
{
"status": "ON_HOLD",
"order_ids": [
10293,
34522
],
"message": "please fund your account to process this order"
}
on hold instead of being processed.
You have up to 24 hours to fund your wallet and complete the transaction. Once sufficient funds are available, the order will be automatically resumed and processed.
If the wallet is not funded within this timeframe, the order may expire and will need to be created again.
| Error Code | HTTP | Description |
|---|---|---|
INVALID_REQUEST | 400 | Malformed request or missing fields |
INVALID_TRANSACTION_CODE | 400 | Transaction code is incorrect |
PRODUCT_NOT_FOUND | 404 | One or more products not found |
INVALID_SHIPPING | 400 | Shipping config or method is invalid |
SHIPPING_FEE_MISMATCH | 400 | Shipping fee no longer valid |
WALLET_INSUFFICIENT | 402 | Order placed on hold due to insufficient wallet balance |
/v1/order/{orderID}
Retrieve full details of a specific order, including all products, vendor breakdown, shipping details, and customer information.
This endpoint returns a complete order history record along with all associated product items under the order.
# Get order by ID
curl https://escrow.payuee.com/v1/order/123 \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
{
"success": true,
"data": {
"id": 123,
"order_status": "processing",
"received_product": false,
"order_cost": 45000,
"shipping_cost": 2500,
"order_sub_total_cost": 47500,
"shipping_method": "distance_based",
"shipping_company": "DHL",
"customer_email": "jane@email.com",
"customer_fname": "Jane",
"customer_user_sname": "Okafor",
"customer_phone_number": "+2348099001122",
"customer_state": "Lagos",
"customer_city": "Lekki",
"customer_street_address_1": "22 Adeola Odeku Street",
"delivery_time": "2024-01-09T10:30:00Z",
"estimated_delivery": 3,
"product_orders": [
{
"product_id": 12,
"title": "Classic Shirt",
"quantity": 2,
"order_cost": 20000,
"currency": "NGN",
"outfit_size": "M",
"first_image_url": "https://cdn.payuee.com/products/img1.jpg",
"product_image": [
{
"url": "https://cdn.payuee.com/products/img1.jpg"
}
]
}
]
}
}
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
ORDER_NOT_FOUND |
404 | No order found for the provided ID |
FORBIDDEN |
403 | You do not have permission to access this order |
INVALID_ID |
400 | Order ID is invalid |
SERVER_ERROR |
500 | Failed to retrieve order details |
/v1/order/list
Retrieve a paginated list of orders created under your integration. Use query parameters to control pagination and navigate through results.
page and limit query parameters.
Defaults: page=1, limit=10.
# Get paginated orders
curl "https://escrow.payuee.com/v1/order/list?page=1&limit=15" \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
| Query Param | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Page number (default: 1) |
limit |
integer | No | Number of records per page (default: 15) |
{
"success": true,
"data": [
{
"id": 123,
"order_status": "processing",
"order_cost": 45000,
"shipping_cost": 2500,
"customer_email": "jane@email.com"
}
],
"pagination": {
"current_page": 1,
"next_page": 2,
"previous_page": 0,
"total_pages": 5,
"all_records": 50
},
"store_name": "Payuee Store"
}
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
INVALID_PAGINATION |
400 | Invalid page or limit values |
SERVER_ERROR |
500 | Failed to retrieve orders |
/v1/order/scan-qr
Scan and validate a customer's QR code before completing delivery verification. This endpoint marks the order as scanned and is a required step before calling the final verification endpoint.
This ensures that:
- 📦 The vendor is physically present with the customer
- 🔐 The correct order is being verified
- 🛡️ Fraudulent confirmations are prevented
/v1/order/verify. If this step is skipped, verification will fail.
# Scan customer QR code
curl https://escrow.payuee.com/v1/order/scan-qr \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"encrypted": "ENCRYPTED_QR_PAYLOAD"
}'
| Field | Type | Required | Description |
|---|---|---|---|
encrypted |
string | Yes | Encrypted payload obtained from scanning the customer's QR code |
Payuee provides a lightweight JavaScript helper to simplify QR code scanning and extraction.
This allows you to scan the QR code and directly send the encrypted payload to the API.
<script src="https://escrow.payuee.com/payuee-qrcode.js"></script>
reader.
You must include this in your UI where you want the camera scanner to appear.
<!-- QR Scanner Render Container -->
<div id="reader"></div>
// Initialize scanner
const scanner = new PayueeV1QrcodeScanner("reader", {
fps: 10,
qrbox: { width: 250, height: 250 }
});
let isScanning = false;
// When QR is successfully scanned
async function onScanSuccess(decodedText) {
if (isScanning) return;
isScanning = true;
try {
await fetch("https://escrow.payuee.com/v1/order/scan-qr", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_SECRET_KEY",
"X-Payuee-Public-Key": "YOUR_PUBLIC_KEY",
"X-Payuee-Timestamp": "1710000000",
"X-Payuee-Signature": "GENERATED_SIGNATURE"
},
body: JSON.stringify({
encrypted: decodedText
})
});
} catch (err) {
console.error("Scan failed:", err);
}
scanner.clear();
isScanning = false;
}
// Handle scan errors
function onScanFailure(error) {
console.warn("QR scan error:", error);
}
// Start scanning
scanner.render(onScanSuccess, onScanFailure);
decodedText value returned from the scanner is already the
encrypted payload required by the API. No additional transformation is needed.
{
"success": "successfully updated order scanned status"
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
INVALID_QR |
400 | QR code is invalid or corrupted |
ORDER_NOT_FOUND |
500 | Order not found or does not belong to your account |
ALREADY_SCANNED |
500 | This order has already been scanned |
NOT_AUTHORIZED |
500 | You are not authorized to scan this order |
SERVER_ERROR |
500 | Unexpected error while processing QR scan |
/v1/order/verify
Verify that a customer has successfully received their order. This endpoint is used by the company (developer) to confirm delivery after scanning the customer's QR code and validating the customer's transaction code.
This process ensures secure escrow release by combining:
- 🔐 A QR code scan (generated per order)
- 🔑 The customer's transaction code
- The QR code is valid and matches the order
- The transaction code is correct
- The order belongs to your company (developer)
# Verify customer order (QR + transaction code)
curl https://escrow.payuee.com/v1/order/verify \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"encrypted": "ENCRYPTED_QR_PAYLOAD",
"customer_id": 45,
"trans_code": "123456"
}'
| Field | Type | Required | Description |
|---|---|---|---|
encrypted |
string | Yes | Encrypted payload obtained from scanning the customer's QR code |
customer_id |
integer | Yes | Customer identifier associated with the order |
trans_code |
string | Yes |
Customer's secure transaction code used to authorize and confirm delivery. This must match the code set by the customer during checkout. |
{
"success": "successfully updated order status"
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
INVALID_QR |
400 | QR code is invalid or corrupted |
WRONG_TRANSACTION_CODE |
400 | Transaction code provided does not match the customer's code |
ORDER_NOT_FOUND |
404 | No order found for the provided QR payload |
NOT_AUTHORIZED |
500 | You can only verify orders that belong to your company |
QR_REQUIRED |
500 | QR code must be scanned before verification can proceed |
SERVER_ERROR |
500 | Unexpected error while verifying order |
/v1/order/report
Report an issue with an order. This action flags the entire order and automatically places the associated vendor under review.
When an order is reported:
- 🚩 The vendor is flagged for investigation
- 📦 All products linked to that order may be placed under monitoring
- 🛡️ The platform can take action to prevent further issues
order level.
You do not need to provide product IDs. The system automatically traces all
products and the vendor involved in the transaction.
# Report an order
curl https://escrow.payuee.com/v1/order/report \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"order_id": 1024,
"report_note": "Customer reported damaged product on delivery"
}'
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
integer | Yes | Unique identifier of the order being reported |
report_note |
string | Yes | Detailed explanation of the issue (e.g. damaged item, wrong delivery, fraud suspicion) |
{
"success": "successfully reported order"
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
ORDER_NOT_FOUND |
404 | The specified order does not exist |
NOT_ALLOWED |
404 | You do not have permission to report this order |
INVALID_REQUEST |
400 | Request body is malformed or missing required fields |
SERVER_ERROR |
500 | Unexpected error while processing the report |
/v1/order/cancel
Cancel an order within the allowed cancellation window. This endpoint enables safe transaction reversal before fulfillment progresses too far.
Orders can only be cancelled within the first 30% of the delivery timeline.
After this period, cancellation is no longer allowed and the order must instead be
reported if any issue occurs.
transaction code to authorize
and securely validate the request.
# Cancel an order
curl https://escrow.payuee.com/v1/order/cancel \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json"
-d '{
"order_id": 1024,
"trans_code": "123456",
"report_note": "Customer requested cancellation due to wrong address"
}'
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
integer | Yes | Unique identifier of the order to cancel |
trans_code |
string | Yes | Customer transaction code used to authorize cancellation |
report_note |
string | No | Optional note describing the reason for cancellation. If provided, the order will also be flagged for review. |
{
"success": "successfully updated status"
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
ORDER_NOT_FOUND |
404 | The specified order does not exist |
INVALID_TRANSACTION_CODE |
400 | Incorrect transaction code provided |
CANCELLATION_WINDOW_EXPIRED |
403 | Order can no longer be cancelled (past 30% delivery timeline) |
NOT_ALLOWED |
403 | User does not have permission to cancel this order |
ALREADY_REFUNDED |
404 | The order has already been refunded |
REFUND_ERROR |
403 | Error occurred while processing refund |
SERVER_ERROR |
500 | Unexpected error while cancelling order |
/v1/product/review
Submit a customer review for a purchased product. Reviews are only accepted for products that have been successfully linked to an order history under your integration.
When a review is submitted:
- ⭐ The product rating is stored and linked to the purchase record
- 🛒 The system verifies that the customer actually ordered the product
- 🔒 Unauthorized review attempts are blocked automatically
user_id, product_id, and your developer account
are all connected to the same purchase record before saving.
# Submit product review
curl https://escrow.payuee.com/v1/product/review \
-X POST \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID" \
-H "Content-Type: application/json" \
-d '{
"product_id": 450,
"user_id": 1221,
"name": "John Doe",
"email": "john@example.com",
"review": "Excellent quality, arrived exactly as described.",
"rating": 5,
}'
| Field | Type | Required | Description |
|---|---|---|---|
product_id |
integer | Yes | Unique identifier of the purchased product |
user_id |
integer | Yes | Customer ID that placed the order containing the product |
name |
string | Yes | Customer display name shown with the review |
email |
string | Yes | Email of the reviewer |
review |
string | Yes | Customer review text |
rating |
integer | Yes | Rating score from 1 to 5 |
{
"success": {
"id": 55,
"product_id": 450,
"user_id": 1221,
"name": "John Doe",
"review": "Excellent quality, arrived exactly as described.",
"rating": 5
}
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
INVALID_REQUEST |
400 | email is required |
PRODUCT_NOT_FOUND |
404 | The specified product does not exist |
PURCHASE_REQUIRED |
403 | Customer has not purchased this product |
DUPLICATE_REVIEW |
409 | Customer already reviewed this purchased product |
INVALID_RATING |
400 | Rating must be between 1 and 5 |
INVALID_REQUEST |
400 | Request body is malformed or missing required fields |
SERVER_ERROR |
500 | Unexpected error while processing review |
/v1/product/reviews/{page_number}/{product_id}
Retrieve customer reviews for a specific product. Results are paginated and returned in descending order based on the latest submitted reviews.
This endpoint allows you to:
- ⭐ View verified customer reviews for a product
- 📄 Paginate review records in groups of 4 per request
- 📊 Access total review count for frontend pagination
4 reviews.
Use the count field in the response to determine total pages.
# Get reviews for product
curl https://escrow.payuee.com/v1/product/reviews/1/450 \
-X GET \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "X-Payuee-Public-Key: YOUR_PUBLIC_KEY" \
-H "X-Payuee-Timestamp: 1710000000" \
-H "X-Payuee-Signature: GENERATED_SIGNATURE" \
-H "X-Payuee-Idempotency-Key: UNIQUE_REQUEST_ID"
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
integer | Yes | Page number to fetch (starts from 1) |
product_id |
integer | Yes | Unique identifier of the product |
{
"success": [
{
"id": 11,
"product_id": 450,
"name": "John Doe",
"review": "Very good quality and fast delivery.",
"rating": 5,
"created_at": "2026-05-13T10:15:00Z"
}
],
"count": 27
}
| Error | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing API credentials |
PRODUCT_NOT_FOUND |
404 | Specified product does not exist |
INVALID_PAGE |
400 | Page number must be a valid positive integer |
SERVER_ERROR |
500 | Unexpected error while retrieving reviews |
Event Delivery System
Payuee webhooks are securely delivered using HMAC-SHA256 signed payloads. Every event is encrypted and signed before being sent to your server to ensure integrity, authenticity, and replay protection.
Payuee webhooks are protected using HMAC-SHA256 signed payloads to ensure:
- ✔ Request authenticity (sent only by Payuee)
- ✔ Payload integrity (no tampering in transit)
- ✔ Replay protection using timestamps
Signature Generation (Payuee Side)
payload = timestamp + UPPERCASE(METHOD) + request_path + request_body
signature = HMAC_SHA256(payload, webhook_secret)
Headers Sent With Every Webhook:
X-Payuee-Signature→ HMAC signatureX-Payuee-Timestamp→ UNIX timestamp (seconds)X-Payuee-Public-Key→ Your registered webhook identity keyAuthorization: Bearer WEBHOOK_SECRET→ used for key resolution
IP Whitelisting (Security Layer)
For additional security, Payuee webhook requests will always originate from the following static IP address:
- ✔
84.8.135.142
Verification Flow (Your Server)
Your server must:
- Reconstruct the payload exactly as received
- Recompute HMAC using your webhook secret
- Compare signatures using constant-time comparison
- Validate timestamp (reject if older than 5 minutes)
Delivery Guarantee
Payuee will continue retrying webhook delivery until your server responds with:
200 OK + { "status": "success" }
Any response other than HTTP 200 is considered a failed delivery and will trigger automatic retries.
{
"event_type": "order.created",
"order_id": 1024,
"status": "processing",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "processing",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.on_hold",
"order_id": 1024,
"status": "on_hold",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "on_hold",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.scanned",
"order_id": 1024,
"status": "processing",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "processing",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.delivered",
"order_id": 1024,
"status": "successful",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "successful",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.refunded",
"order_id": 1024,
"status": "successful",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "cancelled",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.cancelled",
"order_id": 1024,
"status": "cancelled",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "cancelled",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
{
"event_type": "order.report",
"order_id": 1024,
"status": "successful",
"order": {
"id": 1024,
"eshop_user_id": 12,
"developer_id": 3,
"user_id": 88,
"shipper_id": 5,
"order_status": "successful",
"received_product": false,
"quantity": 2,
"order_cost": 25000.00,
"initial_cost": 20000.00,
"selling_price": 25000.00,
"order_sub_total_cost": 24000.00,
"shipping_cost": 1000.00,
"shipping_method": "standard",
"shipping_company": "DHL",
"order_discount": 0,
"customer_email": "john@example.com",
"customer_fname": "John",
"customer_user_sname": "Doe",
"customer_company_name": "",
"customer_state": "Rivers",
"customer_city": "Port Harcourt",
"customer_street_address_1": "12 Aba Road",
"customer_zip_code": "500001",
"customer_phone_number": "+2348012345678",
"scanned_qr_code": false,
"credit_processed": false,
"reposted": false,
"original_eshop_user_id": 0,
"reposted_selling_price": 0,
"product_orders": [
{
"product_id": 55,
"title": "Nike Sneakers",
"quantity": 1,
"order_cost": 20000,
"currency": "NGN",
"reposted": false,
"first_image_url": "https://cdn.payuee.com/product.jpg"
}
]
},
"timestamp": "2026-04-26T10:00:00Z"
}
Payuee will retry webhook delivery if your server does NOT return
200 OK.✔ Retry continues until success is confirmed
✔ Event is marked delivered only after
200 OK✔ Duplicate protection is handled using event IDs
End-to-End Integration Flow
This guide walks you through a complete successful order flow and a failure/refund scenario.
Successful Order Flow
Failure & Refund Flow
Webhooks
Payuee delivers real-time event notifications to your registered endpoint via HTTPS POST requests. Each event is signed with a secret so you can verify its authenticity.
Verifying Webhook Signatures
Every webhook payload is signed. Always verify the signature before processing.
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret, timestamp) {
// Payuee signature format
const signedPayload = timestamp + '.' + payload;
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(signedPayload).digest('hex');
const expected = `sha256=${digest}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js webhook
app.post(
'/webhooks/payuee',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['x-payuee-signature'];
const timestamp = req.headers['x-payuee-timestamp'];
if (!verifyWebhook(
req.body,
sig,
process.env.PAYUEE_WEBHOOK_SECRET,
timestamp
)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Handle event...
res.json({ received: true });
}
);
import hmac
import hashlib
import os
def verify_webhook(payload: bytes, signature: str, secret: str, timestamp: str) -> bool:
# Payuee signature format: timestamp + "." + raw_body
signed_payload = timestamp.encode() + b'.' + payload
mac = hmac.new(
secret.encode(),
msg=signed_payload,
digestmod=hashlib.sha256
)
expected = f"sha256={mac.hexdigest()}"
return hmac.compare_digest(expected, signature)
# Flask example
@app.route('/webhooks/payuee', methods=['POST'])
def handle_webhook():
payload = request.get_data()
sig = request.headers.get('X-Payuee-Signature')
timestamp = request.headers.get('X-Payuee-Timestamp')
if not verify_webhook(
payload,
sig,
os.environ['PAYUEE_WEBHOOK_SECRET'],
timestamp
):
return 'Invalid signature', 401
event = request.get_json()
# Handle event...
return {'received': True}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"io"
"os"
)
// verifyWebhook validates Payuee webhook signatures
func verifyWebhook(payload []byte, signature, secret, timestamp string) bool {
// Signature format: timestamp + "." + raw_body
signedPayload := timestamp + "." + string(payload)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signedPayload))
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
sig := r.Header.Get("X-Payuee-Signature")
timestamp := r.Header.Get("X-Payuee-Timestamp")
if !verifyWebhook(
body,
sig,
os.Getenv("PAYUEE_WEBHOOK_SECRET"),
timestamp,
) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Handle verified webhook event here
w.Write([]byte(`{"received": true}`))
}
<?php
// Verify Payuee Webhook Signature (with timestamp)
function verifyWebhook($payload, $signature, $secret, $timestamp) {
$signedPayload = $timestamp . "." . $payload;
$computed = hash_hmac('sha256', $signedPayload, $secret);
$expected = "sha256=" . $computed;
return hash_equals($expected, $signature);
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYUEE_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_PAYUEE_TIMESTAMP'] ?? '';
$secret = getenv('PAYUEE_WEBHOOK_SECRET');
if (!verifyWebhook($payload, $signature, $secret, $timestamp)) {
http_response_code(401);
exit("Invalid signature");
}
$event = json_decode($payload, true);
echo json_encode(['received' => true]);
?>
<?php
// Verify Payuee Webhook Signature (with timestamp)
function verifyWebhook($payload, $signature, $secret, $timestamp) {
$signedPayload = $timestamp . "." . $payload;
$computed = hash_hmac('sha256', $signedPayload, $secret);
$expected = "sha256=" . $computed;
return hash_equals($expected, $signature);
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYUEE_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_PAYUEE_TIMESTAMP'] ?? '';
$secret = getenv('PAYUEE_WEBHOOK_SECRET');
if (!verifyWebhook($payload, $signature, $secret, $timestamp)) {
http_response_code(401);
exit("Invalid signature");
}
$event = json_decode($payload, true);
echo json_encode(['received' => true]);
?>
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.web.bind.annotation.*;
@RestController
public class WebhookController {
@PostMapping("/webhooks/payuee")
public String handleWebhook(
@RequestBody String payload,
@RequestHeader("X-Payuee-Signature") String signature,
@RequestHeader("X-Payuee-Timestamp") String timestamp
) throws Exception {
String secret = System.getenv("PAYUEE_WEBHOOK_SECRET");
String signedPayload = timestamp + "." + payload;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
String expected = "sha256=" +
javax.xml.bind.DatatypeConverter.printHexBinary(
mac.doFinal(signedPayload.getBytes())
).toLowerCase();
if (!expected.equals(signature)) {
return "Invalid signature";
}
return "received";
}
}
using System.Security.Cryptography;
using System.Text;
[ApiController]
[Route("webhooks/payuee")]
public class WebhookController : ControllerBase
{
[HttpPost]
public IActionResult Handle()
{
var secret = Environment.GetEnvironmentVariable("PAYUEE_WEBHOOK_SECRET");
var payload = new StreamReader(Request.Body).ReadToEnd();
var signature = Request.Headers["X-Payuee-Signature"].ToString();
var timestamp = Request.Headers["X-Payuee-Timestamp"].ToString();
var signedPayload = timestamp + "." + payload;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedPayload));
var expected = "sha256=" + Convert.ToHexString(hash).ToLower();
if (expected != signature)
return Unauthorized("Invalid signature");
return Ok(new { received = true });
}
}
require 'openssl'
require 'json'
class PayueeWebhookController < ApplicationController
skip_before_action :verify_authenticity_token
# Verify Payuee Webhook Signature
def verify_webhook(payload, signature, secret)
digest = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
expected = "sha256=#{digest}"
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
def create
payload = request.body.read
signature = request.headers['X-Payuee-Signature']
secret = ENV['PAYUEE_WEBHOOK_SECRET']
unless verify_webhook(payload, signature, secret)
render json: { error: 'Invalid signature' }, status: :unauthorized
return
end
event = JSON.parse(payload)
# Handle event...
render json: { received: true }
end
end
Error Handling
All errors follow a consistent JSON structure. Use the code field for programmatic handling and message for human-readable context.
{
"error": {
"code": "INVALID_AMOUNT",
"message": "Amount must be a positive integer in the smallest currency unit",
"field": "amount",
"request_id": "req_8xKmNpLrQ9"
}
}
HTTP Status Codes
| Status | Meaning |
|---|---|
200 | Success — request processed correctly |
201 | Created — resource was successfully created |
400 | Bad Request — validation error, check the field in response |
401 | Unauthorized — missing or invalid API key |
402 | Payment Required — wallet balance insufficient |
403 | Forbidden — action not permitted (e.g. wallet frozen) |
404 | Not Found — resource does not exist |
409 | Conflict — duplicate idempotency key or reference |
429 | Rate Limited — slow down requests (default: 1000 req/min) |
500 | Server Error — Payuee internal error (retry with backoff) |
Retry Strategy
async function withRetry(fn, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
const isRetryable = [429, 500, 502, 503].includes(err.status);
if (!isRetryable || attempt === maxAttempts) throw err;
const delay = Math.min(1000 * 2 ** attempt, 30000);
await new Promise(r => setTimeout(r, delay));
}
}
}
SDKs & Code Examples
Payuee provides a lightweight JavaScript helper to simplify QR code scanning and extraction. This allows you to scan the QR code and directly send the encrypted payload to the API.
<script src="https://escrow.payuee.com/payuee-qrcode.js"></script>
reader.
You must include this in your UI where you want the camera scanner to appear.
<!-- QR Scanner Render Container -->
<div id="reader"></div>
// Initialize scanner
const scanner = new PayueeV1QrcodeScanner("reader", {
fps: 10,
qrbox: { width: 250, height: 250 }
});
let isScanning = false;
// When QR is successfully scanned
async function onScanSuccess(decodedText) {
if (isScanning) return;
isScanning = true;
try {
await fetch("https://escrow.payuee.com/v1/order/scan-qr", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_SECRET_KEY",
"X-Payuee-Public-Key": "YOUR_PUBLIC_KEY",
"X-Payuee-Timestamp": "1710000000",
"X-Payuee-Signature": "GENERATED_SIGNATURE"
},
body: JSON.stringify({
encrypted: decodedText
})
});
} catch (err) {
console.error("Scan failed:", err);
}
scanner.clear();
isScanning = false;
}
// Handle scan errors
function onScanFailure(error) {
console.warn("QR scan error:", error);
}
// Start scanning
scanner.render(onScanSuccess, onScanFailure);
decodedText value returned from the scanner is already the
encrypted payload required by the API. No additional transformation is needed.
Security & Best Practices
API Key Protection
- Never expose API keys in client-side code or public repositories
- Use environment variables or a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault)
- Rotate keys immediately if you suspect compromise
- Use separate keys for sandbox and production
- Enable key expiry and IP allowlists in the dashboard
Webhook Verification
- Always verify the
X-Payuee-Signatureheader using HMAC-SHA256 - Use
crypto.timingSafeEqual(not string comparison) to prevent timing attacks - Process raw request body — parsed JSON may differ
- Reject requests with missing or invalid signatures immediately
- Store webhook secrets separate from API keys
Fraud Prevention
- Payuee's escrow model prevents payment fraud by design — funds never leave escrow until verified delivery
- Use the verification code from order creation — it's one-time-use and address-bound
- Monitor webhook events for unexpected state changes
- Implement rate limiting on your order creation endpoint
- Use idempotency keys to prevent duplicate orders from retries
Idempotency
- Include a unique
X-Payuee-Idempotency-Keyheader on all POST requests - Idempotency keys must be unique per operation, not per session
- Keys expire after 24 hours — generate fresh ones for retries after that window
- Safe to retry with the same key if the original request timed out
X-Payuee-Idempotency-Key: order-20240106-user-7723-001