All systems operational

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.

99.99%
Uptime SLA
<200ms
API Response
256-bit
AES Encryption
Escrow Architecture
Compliant

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.

graph TB A[Your Platform] -->|Fund Wallet| B[Payuee Wallet] A -->|Create Order| C[Escrow Engine] B -->|Lock Funds| C C -->|Notify| D[Vendor] D -->|Fulfill Order| E{Delivery Method} E -->|Payuee Logistics| F[QR Scan Verification] E -->|Vendor Delivery| G[API Confirmation] F --> H[Escrow Release] G --> H H -->|Commission Split| I[Settlement] I -->|Vendor Share| D I -->|Platform Fee| A C -->|Timeout/Fail| J[Refund] J -->|Return Funds| B style A fill:#6366f1,color:#fff,stroke:none style B fill:#0ea5e9,color:#fff,stroke:none style C fill:#8b5cf6,color:#fff,stroke:none style H fill:#10b981,color:#fff,stroke:none style J fill:#ef4444,color:#fff,stroke:none

Core Integration Flow

01
Wallet Funding Pre-fund your Payuee wallet via bank transfer or supported payment gateway.
02
Product Access Fetch and display Payuee-managed products via the Products API.
03
Order Creation Create an order — required funds are automatically locked in escrow.
04
Vendor Notification Payuee notifies the assigned vendor & logistics company with fulfillment details.
05
Delivery & Verification Delivery is completed and verified via QR scan or API confirmation.
06
Escrow Release & Settlement Funds are released and automatically distributed based on predefined commission rules.

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.

💡
Integration Note: Payuee currently operates in a production environment. We recommend testing with small transaction amounts while integrating. A dedicated sandbox environment will be available in a future release.

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.

bash
# 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"
200 OK
{
  "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.

http

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 TypePrefixUsage
Public Keypayuee_pk_Included in all requests for identification.
Secret Keypayuee_sk_Used for request signing and authorization
⚠️
Security Notice:
  • Never expose your Secret Key in client-side code
  • Do not commit API keys to public repositories
  • Store keys securely using environment variables or a secrets manager
  • All requests must be generated from a secure backend
  • 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

    Sandbox
    Payuee does not currently provide a sandbox environment. To safely test your integration:
    • Use low-value transactions during development
    • Start with authentication and product retrieval endpoints
    • Gradually test order creation and delivery flows
    Production
    https://escrow.payuee.com/v1
    • 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
    Your Company
    Pre-funded Wallet
    Payuee Engine
    Escrow Lock
    Vendor
    Settlement

    Wallet States

    StateDescription
    ACTIVEWallet is funded and ready to process orders
    LOW_BALANCEBalance is low; new orders may be placed on HOLD
    INSUFFICIENTOrder amount exceeds available balance; order is placed on HOLD
    FROZENWallet is restricted due to compliance or administrative review
    ⚠️
    Important: Payuee enforces all order execution rules. Funds are never partially processed — an order either has sufficient balance and proceeds, or is placed on 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.

    Primary Flow:
    CREATED
    ESCROW_LOCKED
    CONFIRMED
    DELIVERED
    RELEASED
    Alternative paths: HOLD FAILED REFUNDED CANCELLED
    StatusDescriptionNext Steps
    CREATEDOrder is received and validated by PayueeSystem attempts escrow lock
    ESCROW_LOCKEDFunds are successfully locked in escrowVendor is notified for fulfillment
    CONFIRMEDVendor has accepted and is preparing the orderAwait delivery
    DELIVEREDDelivery is verified via QR scan or API confirmationTrigger escrow release
    RELEASEDFunds are distributed to all partiesOrder completed
    HOLDInsufficient wallet balanceFund wallet to resume → moves to ESCROW_LOCKED
    FAILEDDelivery failed or order expiredRefund initiated
    REFUNDEDFunds returned to walletOrder closed
    CANCELLEDOrder cancelled before fulfillment or fundingOrder closed
    ⚠️
    Important: Payuee strictly enforces state transitions. Orders cannot skip states or move backward outside of defined flows.

    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%)
    Live Example: Order Settlement Calculator
    Order Total
    ₦0
    Logistics Fee (deducted first)
    ₦0
    Product Value (after logistics)
    ₦0
    Escrow Fee (2%)
    ₦0
    API Partner Share (30%)
    ₦0
    Payuee Share (70%)
    ₦0
    Vendor Receives
    ₦0
    ⚠️
    Important: Logistics fees are processed independently and are not included in escrow fee calculations or revenue sharing.

    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.

    1 Payuee generates a secure QR code for the order
    2 QR is attached to the package or delivery record
    3 Buyer scans QR Code at point of delivery
    4 Payuee validates order authenticity and delivery state
    🔐

    6-Digit PIN Confirmation

    After successful QR validation, the buyer must enter a secure 6-digit transaction PIN to authorize final escrow release.

    1 Buyer receives prompt to enter transaction PIN
    2 PIN is verified against encrypted escrow record
    3 If valid, escrow is marked as confirmed
    4 Funds are automatically released to vendor and partners

    API Reference

    Use the following base URL to make API requests:

    https://escrow.payuee.com/v1

    ℹ️
    All API requests must be authenticated using your Public Key and Secret Key.

    Include the following headers in every request:
    • Authorization: Bearer YOUR_SECRET_KEY
    • X-Payuee-Public-Key: Your public API key
    • X-Payuee-Timestamp: Current UNIX timestamp (seconds)
    • X-Payuee-Signature: HMAC SHA256 signature
    • X-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.

    ⚠️
    Before creating orders, ensure your prefunded wallet contains sufficient balance.

    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.
    🔐
    Idempotency (Preventing Duplicate Orders)

    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.

    POST /v1/products

    Retrieve available products from Payuee vendors. Supports filtering by category, price range, weight, distance, tags, and sorting options. Results are paginated.

    ParameterTypeDescription
    categorystringFilter by category: outfits, jewelry, kids-accessories, cars-car-parts, tools, gadgets, others. Default: all
    user_latfloatUser latitude for location-based results
    user_lonfloatUser longitude for location-based results
    max_distanceintegerMax distance (km) from vendor (default: 10)
    min_pricefloatMinimum price filter
    max_pricefloatMaximum price filter
    min_weightfloatMinimum weight (kg)
    max_weightfloatMaximum weight (kg)
    page_numberintegerPagination page (default: 1)
    sort_option integer Sorting option used to control product ordering.
    View available sorting options
    1 → Featured products
    2 → Best selling products
    3 → Product title (A → Z)
    4 → Product title (Z → A)
    5 → Price (Low → High)
    6 → Price (High → Low)
    7 → Oldest products first
    8 → Newest products first
    tagsarrayFilter by product tags
    🔐
    This endpoint requires authentication using your API keys and signature headers.
    bash
    # 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
          }'
        
    200 OK
    {
      "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
        }
      ]
    }
    🖼️
    Product image URLs are returned as relative paths. To display images, prepend:

    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
    GET /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.

    ParameterTypeRequiredDescription
    id integer Yes The unique ID of the product
    product_url_id string No The unique URL ID of the product
    🔐
    This endpoint requires authentication using your API keys and signature headers.
    bash
    # 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" 
        
    200 OK
    {
      "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
    GET /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.

    This endpoint requires authentication headers only. No request body is needed.
    bash
    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" 
      
    200 OK
    {
      "status": "success",
      "wallet_balance": 250000,
      "currency": "NGN"
    }
    💰
    The 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
    GET /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.

    bash
    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"
         
    200 OK
    {
      "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
    GET /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.

    bash
    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"
    
    200 OK
    {
          "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
    GET /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.

    📍
    The display field is preformatted for direct use in dropdowns or address selection interfaces (e.g. "Lekki - Phase 1").
    bash
    # 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 ParamTypeRequiredDescription
    state string Yes Name of the state (must match values from /location/states)
    🔗
    Use this endpoint after retrieving states from /location/states to build a complete delivery location selector.
    200 OK
    {
      "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
    POST /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.

    🚚
    Shipping is calculated per vendor. If your cart contains products from multiple vendors, each vendor will return its own shipping fee and logistics provider. Always use the exact values returned here when submitting the order to avoid validation errors.
    bash
    
      # 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
        }
      ]
    }'
    
    FieldTypeRequiredDescription
    vendorsarrayYesList of vendor IDs in the cart
    statestringYesDelivery state
    citystringYesDelivery city
    latitudefloatYesBuyer latitude (improves distance-based pricing)
    longitudefloatYesBuyer longitude
    cart_itemsarrayYesCart items used for shipping calculation
    cart_items[].product_idintegerYesProduct ID
    cart_items[].eshop_user_idintegerYesVendor ID for the product (same as eshop_user_id from product data)
    cart_items[].eshop_user_idintegerYesVendor ID for the product
    cart_items[].quantityintegerYesQuantity of the product
    🧩
    The 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.
    200 OK
    {
          "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
    POST /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.

    Orders can include products from multiple vendors. Each vendor requires a valid shipping selection. Shipping fees are validated server-side and must match the latest configuration before the order can be processed.
    The 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.
    FieldTypeRequiredDescription
    trans_codestringYesSecure transaction code for authorizing the order
    webhook_response_urlstringYesURL to receive webhook responses.

    Takes precedence over dashboard webhook URL. Falls back if not provided.
    customerobjectYesCustomer information and delivery details
    customer.emailstringYesCustomer email address
    customer.first_namestringYesFirst name
    customer.last_namestringYesLast name
    customer.phone_numberstringYesPhone number
    customer.statestringYesDelivery state
    customer.citystringYesDelivery city
    customer.address_1stringYesPrimary address
    customer.address_2stringNoSecondary address
    customer.order_notestringNoOrder notes from customer
    customer.latitudefloatYesPrecise delivery latitude (improves shipping accuracy)
    customer.longitudefloatYesPrecise delivery longitude
    cart_itemsarrayYesList of products to order
    cart_items[].product_idintegerYesProduct ID
    cart_items[].cart_meta.quantityintegerYesQuantity
    cart_items[].cart_meta.outfit_sizestringNoSelected size (if applicable)
    shippingarrayYesShipping selection per vendor
    shipping[].vendor_idintegerYesVendor identifier
    shipping[].feefloatYesCalculated shipping fee
    shipping[].method_idstringYesShipping method (distance_based, weight_based, etc.)
    shipping[].config_idintegerYesShipping configuration ID
    shipping[].company_namestringYesLogistics provider name
    bash
    
      # 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"
        }
      ]
    }'
    200 OK
    {
      "success": true,
      "order_ids": [
        10293,
        34523
      ],
      "message": "order created successfully"
    }
    ON HOLD
    {
      "status": "ON_HOLD",
      "order_ids": [
        10293,
        34522
      ],
      "message": "please fund your account to process this order"
    }
    If your wallet balance is insufficient at the time of order creation, the order will be placed 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 CodeHTTPDescription
    INVALID_REQUEST400Malformed request or missing fields
    INVALID_TRANSACTION_CODE400Transaction code is incorrect
    PRODUCT_NOT_FOUND404One or more products not found
    INVALID_SHIPPING400Shipping config or method is invalid
    SHIPPING_FEE_MISMATCH400Shipping fee no longer valid
    WALLET_INSUFFICIENT402Order placed on hold due to insufficient wallet balance
    GET /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.

    🔐
    Access is restricted to the developer that created the order. You can only retrieve orders that belong to your integration.
    bash
    
    # 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" 
    
    200 OK
    {
          "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
    GET /v1/order/list

    Retrieve a paginated list of orders created under your integration. Use query parameters to control pagination and navigate through results.

    📄
    Pagination is controlled using page and limit query parameters. Defaults: page=1, limit=10.
    bash
    
        # 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)
    200 OK
    {
          "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
    POST /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

    ⚠️
    Important: You must successfully scan the QR code before calling /v1/order/verify. If this step is skipped, verification will fail.
    bash
    
    # 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"
    }'
    
    FieldTypeRequiredDescription
    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.

    📦
    Include the Payuee QR helper library at the head of your HTML file:
    <script src="https://escrow.payuee.com/payuee-qrcode.js"></script>
    📍
    Important: The QR scanner will render inside an HTML element with the ID reader. You must include this in your UI where you want the camera scanner to appear.
    html
    
    <!-- QR Scanner Render Container -->
    <div id="reader"></div>
    
    javascript
    
    // 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);
    
    💡
    The decodedText value returned from the scanner is already the encrypted payload required by the API. No additional transformation is needed.
    200 OK
    {
      "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
    POST /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

    ⚠️
    Important: Verification will only succeed if:
    • The QR code is valid and matches the order
    • The transaction code is correct
    • The order belongs to your company (developer)
    Otherwise, the request will be rejected.
    bash
    
    # 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"
    }'
    
    FieldTypeRequiredDescription
    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.
    200 OK
    {
      "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
    POST /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

    ⚠️
    Important: Reporting is done at the order level. You do not need to provide product IDs. The system automatically traces all products and the vendor involved in the transaction.
    🔐
    You can only report orders that belong to your account. Unauthorized attempts will be rejected.
    bash
    
    # 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"
    }'
    
    FieldTypeRequiredDescription
    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)
    200 OK
    {
      "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
    POST /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.

    ⚠️
    Important: Cancellation is time-restricted. Once the order passes 30% of its estimated delivery duration, it becomes locked for delivery and cannot be cancelled.
    🚚
    Logistics Recommendation: Logistics providers are advised to wait until the 30% cancellation window has passed before dispatching items. This reduces failed deliveries, reversals, and operational losses.
    🔐
    Cancellation requires the customer's transaction code to authorize and securely validate the request.
    bash
    
    # 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"
    }'
    
    FieldTypeRequiredDescription
    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.
    200 OK
    {
      "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
    delivery-window-calculator
    POST /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

    ⚠️
    Important: Reviews can only be added to products that are part of a real order. The platform checks that the user_id, product_id, and your developer account are all connected to the same purchase record before saving.
    🔐
    Reviews are tied to verified orders only. Duplicate reviews for the same purchased product are rejected.
    bash
    
        # 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,
        }'
        
    FieldTypeRequiredDescription
    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
    200 OK
    {
      "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
    GET /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

    ℹ️
    Reviews returned are only those linked to verified orders on the platform.
    ⚠️
    Pagination: Each page returns a maximum of 4 reviews. Use the count field in the response to determine total pages.
    bash
    
        # 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"
        
    ParameterTypeRequiredDescription
    page integer Yes Page number to fetch (starts from 1)
    product_id integer Yes Unique identifier of the product
    200 OK
    {
      "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
    WEBHOOKS 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.

    🔐
    How Payuee Webhook Security Works

    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 signature
    • X-Payuee-Timestamp → UNIX timestamp (seconds)
    • X-Payuee-Public-Key → Your registered webhook identity key
    • Authorization: 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
    We strongly recommend whitelisting this IP address on your server firewall to ensure only legitimate Payuee requests are processed.

    Verification Flow (Your Server)
    Your server must:
    1. Reconstruct the payload exactly as received
    2. Recompute HMAC using your webhook secret
    3. Compare signatures using constant-time comparison
    4. 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"
    }
    🔁
    Delivery Retry Policy

    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

    sequenceDiagram participant P as Your Platform participant W as Payuee Wallet participant E as Escrow Engine participant V as Vendor participant D as Delivery Agent P->>W: POST /wallet/fund (pre-fund) W-->>P: Wallet credited P->>E: POST /orders (create order) E->>W: Lock funds atomically W-->>E: Funds locked E-->>P: 201 Created (status: escrow_locked) E->>V: Webhook: order.created V-->>E: Acknowledge order E-->>P: Webhook: order.confirmed V->>D: Dispatch delivery D->>P: Deliver to buyer D->>E: Scan QR / POST /verify-order E->>W: Release escrow funds W->>V: Settle vendor share W->>P: Deduct platform fee E-->>P: Webhook: escrow.released

    Failure & Refund Flow

    sequenceDiagram participant P as Your Platform participant W as Payuee Wallet participant E as Escrow Engine participant V as Vendor P->>E: POST /orders (create order) E->>W: Lock funds W-->>E: Locked alt Wallet Insufficient E-->>P: 402 - Order on HOLD E-->>P: Webhook: wallet.insufficient P->>W: Fund wallet W-->>E: Order resumes end alt Delivery Timeout Note over E: expires_at reached E->>W: Unlock escrow W-->>P: Funds returned to wallet E-->>P: Webhook: order.failed E-->>V: Webhook: order.cancelled end alt Delivery Failed V-->>E: Report failure E->>W: Refund to wallet E-->>P: Webhook: order.failed end

    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.

    javascript
    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 });
        }
      );
    python
    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}
    go
    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
    <?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
    <?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]);
    
      ?>
    java
    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";
          }
      }
    csharp
    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 });
          }
      }
    ruby
    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.

    400 Bad Request
    {
      "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

    StatusMeaning
    200Success — request processed correctly
    201Created — resource was successfully created
    400Bad Request — validation error, check the field in response
    401Unauthorized — missing or invalid API key
    402Payment Required — wallet balance insufficient
    403Forbidden — action not permitted (e.g. wallet frozen)
    404Not Found — resource does not exist
    409Conflict — duplicate idempotency key or reference
    429Rate Limited — slow down requests (default: 1000 req/min)
    500Server Error — Payuee internal error (retry with backoff)

    Retry Strategy

    🔁
    Use exponential backoff for 429 and 5xx errors. Do not retry 4xx client errors — fix the request first. Always use X-Payuee-Idempotency-Key headers on mutating requests so retries are safe.
    javascript
    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.

    📦
    Include the Payuee QR helper library at the head of your HTML file:
    <script src="https://escrow.payuee.com/payuee-qrcode.js"></script>
    📍
    Important: The QR scanner will render inside an HTML element with the ID reader. You must include this in your UI where you want the camera scanner to appear.
    html
    
    <!-- QR Scanner Render Container -->
    <div id="reader"></div>
    
    javascript
    
    // 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);
    
    💡
    The 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-Signature header 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-Key header 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
    http
    X-Payuee-Idempotency-Key: order-20240106-user-7723-001
    On this page