--- title: Payment API tags: [api, payment, reference, request-network, escrow] --- # Payment API > **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)) The payment surface is split across provider-neutral payment routers, Request Network checkout/webhook routes, derived-destination custody routes, and admin safety routes: | Path prefix | File | Purpose | | --- | --- | --- | | `/api/payment/*` | [`paymentControllerRoutes.ts`](../../backend/src/services/payment/paymentControllerRoutes.ts) | New controller pattern (CRUD + configuration) | | `/api/payment/*` | [`paymentRoutes.ts`](../../backend/src/services/payment/paymentRoutes.ts) | Additional legacy endpoints (tx fetch, exports) | | `/api/payment/request-network/*` | [`requestNetwork/requestNetworkRoutes.ts`](../../backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts) | Request Network intent creation, in-house checkout payloads, webhook processing | | `/api/payment/derived-destinations/*` | [`wallets/derivedDestinationRoutes.ts`](../../backend/src/services/payment/wallets/derivedDestinationRoutes.ts) | Derived destination inspection, balance checks, and sweeping | | `/api/payment/decentralized/*` | [`decentralizedPaymentRoutes.ts`](../../backend/src/services/payment/decentralizedPaymentRoutes.ts) | Legacy wallet-direct confirmations | | `/api/admin/rn/networks/*` | [`requestNetwork/networkRegistryRoutes.ts`](../../backend/src/services/payment/requestNetwork/networkRegistryRoutes.ts) | Request Network chain/token registry | | `/api/admin/payments/awaiting-confirmation/*` | `awaitingConfirmationRoutes.ts` | Admin queue for payments waiting on confirmation/safety checks | Core model: [[Payment]]. Coordination logic to avoid race conditions when multiple sources update the same payment is in `paymentCoordinator.ts`. ## Configuration / health ### POST /api/payment/configuration **Description:** Returns the active payment provider configuration, including Request Network settings, supported chain/token data, receiver/derived-destination context, and redirect/webhook URLs where applicable. **Auth required:** No **Request body:** `{ amount?, currency?, purchaseRequestId? }` (used to scope returned config) **Response 200:** ```json { "accept": [{ "blockchain": "bsc", "token": "0x55d3...", "receiver": "0xa30..." }], "redirect": { "success": "...", "cancel": "..." }, "webhook": "https://.../api/payment/request-network/webhook" } ``` ### GET /api/payment/health **Description:** Lightweight health probe. **Auth required:** No **Response 200:** `{ success, message, endpoints }`. Older builds may still list legacy endpoint names in this health payload; rely on `app.ts` mounts for the authoritative live surface. ### GET /api/payment/shkeeper/config **Description:** Historical compatibility endpoint for the old SHKeeper-hosted widget. It is not part of the current Request Network checkout path. **Auth required:** No ## Payment records (CRUD) ### POST /api/payment **Description:** Create a payment record manually. Normal buyer checkout should use `POST /api/payment/request-network/intents`. **Auth required:** Bearer JWT **Request body:** ```ts { purchaseRequestId: string; sellerOfferId: string; buyerId: string; sellerId: string; amount: { amount: number; currency: string }; blockchain?: { network: string; token: string }; metadata?: Record; } ``` **Response 201:** `{ /* Payment document */ }` ### PUT /api/payment/:id **Description:** Update a payment record (status, transactionHash, metadata). **Auth required:** Bearer JWT **Request body:** ```ts { status?: "pending" | "processing" | "completed" | "failed" | "cancelled"; transactionHash?: string; blockchain?: { ... }; metadata?: Record; } ``` ### GET /api/payment **Description:** List the caller's payments (defaults to `completed,success` status). **Auth required:** Bearer JWT **Query params:** `status`, `limit` (default 50), `offset` (default 0) ### GET /api/payment/:id **Description:** Fetch a payment by id. **Auth required:** Bearer JWT **Errors:** `404` not found. > ⚠️ **NOT IMPLEMENTED:** `GET /payment/:id/status`, `POST /payment/:id/confirm`, and `DELETE /payment/:id` do not exist in the codebase. Do not call these paths. ### GET /api/payment/user/:userId **Description:** Payments for a specific user (admin or self). **Auth required:** Bearer JWT **Query params:** `status`, `limit`, `offset` ### GET /api/payment/stats / GET /api/payment/stats/:userId **Description:** Aggregated counts and sums per status. **Auth required:** Bearer JWT **⚠️ Known undercounting:** Only payments with status `'confirmed'` are counted as `successfulPayments`. Payments with status `'completed'` (the terminal state for SHKeeper and DePay) are **not** included in this count and are therefore under-reported. ### GET /api/payment/export / GET /api/payment/export/:userId **Description:** Export payments as `json` or `csv`. **Auth required:** Bearer JWT **Query params:** `format=json|csv` **⚠️ Privilege gap:** The controller-pattern route for this endpoint has no admin guard. Any authenticated user (not just admins) can export payment data. > ⚠️ **NOT IMPLEMENTED:** `/payment/history`, `/payment/methods`, `/payment/validate`, `/payment/transactions`, and `/payment/escrow/balance` do not exist. Do not call these paths. ### POST /api/payment/payments/cleanup-pending **Description:** Admin cleanup of stale `pending` payments. **Auth required:** Bearer JWT (admin) **Response 200:** `{ success, deletedCount, message }` ### POST /api/payment/payments/:id/fetch-tx **Description:** Re-queries the blockchain to fetch the missing `transactionHash` for a completed payment. **⚠️ SECURITY — NO AUTHENTICATION:** This endpoint has no authentication guard. Any unauthenticated caller can trigger a blockchain re-query for any payment ID. **Response 200:** `{ success, transactionHash, network, source, message }` ### POST /api/payment/payments/auto-fetch-missing **Description:** Batch tx-hash backfill across the database. **⚠️ SECURITY — NO AUTHENTICATION:** This endpoint has no authentication guard. Any unauthenticated caller can trigger a full database backfill scan. **Request body:** `{ limit?: number }` (default 10) ### GET /api/payment/payments/:id/debug **Description:** Debug bundle including the raw payment, blockchain metadata, and wallet-monitor status. Intended for admin / development. **⚠️ SECURITY — NO AUTHENTICATION:** Despite exposing full payment data, this endpoint has no authentication guard. Any unauthenticated caller can retrieve complete payment details for any payment ID. ### POST /api/payment/callback **Description:** Generic payment callback (called by the older client SDK). **Auth required:** No (verified by `paymentRef` matching) **Request body:** `{ paymentId, transactionHash, status, data }` ### POST /api/payment/verify **Description:** Legacy frontend verification endpoint used by the wallet-direct Web3 flow. Confirms a payment and updates the related [[PurchaseRequest]]. **Auth required:** Bearer JWT ## Request Network - Pay-in ### POST /api/payment/request-network/intents **Description:** Creates a Request Network pay-in intent and stores a [[Payment]] with `provider: "request.network"`. The service can attach a per-payment derived destination before creating the provider request. **Auth required:** Bearer JWT (buyer) **Request body:** ```ts { purchaseRequestId: string; sellerOfferId: string; sellerId: string; amount: number; token?: string; // default "USDT" or REQUEST_NETWORK_PAYMENT_CURRENCY network?: string; // default REQUEST_NETWORK_NETWORK or "bsc" metadata?: Record; } ``` **Response 200:** `{ success: true, data: { paymentId, paymentUrl, providerPaymentId, raw, ... } }` ### GET /api/payment/request-network/:paymentId/checkout **Description:** Rehydrates the in-house checkout payload for an existing Request Network payment so the frontend can build the on-chain approval/payment transaction without relying on the hosted RN page. **Auth required:** Bearer JWT (buyer who owns the payment) ### POST /api/payment/request-network/webhook **Description:** Request Network posts settlement updates here. The route verifies `x-request-network-signature` over the raw body, deduplicates delivery IDs, evaluates the Transaction Safety Provider, and coordinates the payment/ledger update. **Auth required:** No (signature-protected) **Response:** `200` when processed or duplicate; `202` when accepted but safety checks are pending; `401` for invalid signature. > ⚠️ **NOT IMPLEMENTED:** `POST /api/payment/request-network/:id/payout/initiate`, `POST /api/payment/request-network/:id/payout/confirm`, `POST /api/payment/request-network/:id/release/confirm`, and `POST /api/payment/request-network/:id/refund/confirm` do not exist in the codebase. Do not call these paths. ## Legacy SHKeeper - Pay-in > [!warning] Historical route family > The current `app.ts` mounts Request Network routes, not `services/payment/shkeeper/*`. Keep this section only for legacy record migration and old operational context. ### POST /api/payment/shkeeper/intents **Description:** Creates a SHKeeper pay-in intent. The server provisions an invoice on SHKeeper, stores a [[Payment]] with `provider: "shkeeper"`, `direction: "in"`, returns the hosted-widget URL. **Auth required:** Bearer JWT (buyer) **Request body:** ```ts { purchaseRequestId: string; sellerOfferId: string; amount: number; sellerId: string; token?: string; // default "USDT" network?: string; // default "bsc" metadata?: Record; } ``` **Response 200:** ```json { "success": true, "data": { "paymentId": "...", "paymentUrl": "https://pay.amn.gg/invoice/...", "externalId": "AMN_...", "expiresAt": "2026-05-23T11:00:00.000Z" } } ``` **Errors:** `400` missing fields, `401` not authenticated, `500` SHKeeper error. **Side effects:** Emits `payment-created` globally and to the request room. ### POST /api/payment/shkeeper/webhook **Description:** SHKeeper posts here when an invoice changes state. Handles both raw-string and JSON bodies and verifies the HMAC signature (`x-shkeeper-signature` against the raw body using `SHKEEPER_WEBHOOK_SECRET`). **Auth required:** No (signature-protected) **Body:** The SHKeeper callback envelope (`external_id`, `crypto`, `addr`, `fiat`, `balance_fiat`, `balance_crypto`, `paid`, `status`, `transactions[]`). **Response 200:** `{ success: true }` **Side effects:** - Updates the matching [[Payment]] to `completed` (`OVERPAID` and `PAID` both count). Note: `'completed'` is the terminal state for SHKeeper payments but is **not** counted as `successfulPayments` in `GET /api/payment/stats`. - Releases or rejects [[SellerOffer]] siblings (the chosen offer becomes `accepted`, others `rejected`). - Updates [[PurchaseRequest]] status to `payment` / `processing`. - Emits `seller-offer-update` to each affected seller room and `purchase-request-update` to the request room. > ⚠️ **NOT IMPLEMENTED:** `GET /api/payment/shkeeper/status/:paymentId` does not exist. SHKeeper payment status is delivered via socket events only — there is no HTTP polling endpoint. ### POST /api/payment/shkeeper/confirm-transaction **Description:** Manual fallback when the webhook misses — the frontend calls this after the buyer signs the EVM transaction directly. Coordinated through `PaymentCoordinator` to avoid double updates. **Auth required:** Bearer JWT **Request body:** `{ paymentId, transactionHash, network? }` **Response 200:** `{ success, message, data: { paymentId, transactionHash, status } }` **Side effects:** Closes the SHKeeper invoice session, then runs the same offer/request updates as the webhook. ### POST /api/payment/shkeeper/test **Description:** Smoke-tests the real SHKeeper API. Development only. **Auth required:** No ### POST /api/payment/shkeeper/callback-test (and GET equivalent) **Description:** Echo-style endpoints used during webhook configuration. **Auth required:** No ### POST /api/payment/shkeeper/create-test-payment **Description:** Inserts a sample [[Payment]] row to exercise the webhook handler. **Auth required:** No ### POST /api/payment/shkeeper/trigger-webhook **Description:** Sends a fake webhook payload to the local webhook endpoint for end-to-end testing. **Auth required:** Bearer JWT ### GET /api/payment/shkeeper/wallet-monitor/status **Description:** Returns the wallet-monitor state (`isMonitoring`, watched wallet addresses). **Auth required:** No ### GET /api/payment/shkeeper/auto-webhook/status **Description:** Returns the auto-webhook fallback monitor state. **Auth required:** No ### GET /api/payment/shkeeper/webhook-stats **Description:** Counters for webhook deliveries (success / failure / duplicates). **Auth required:** Bearer JWT (admin) ## Legacy SHKeeper - Release / Refund (escrow) These build an admin-signed transaction off-chain and require a follow-up confirm with the broadcast tx hash. Source: `shkeeperService.buildAdminSignedTxPayload` and `confirmAdminTx`. **⚠️ Path correction:** The `/shkeeper/` segment is NOT present in the actual release/refund routes. The correct paths are under `/api/payment/:id/…` (not `/api/payment/shkeeper/:id/…`). ### POST /api/payment/:id/release **Description:** Prepares the admin-signed payload to release escrow to the seller. Returns the raw payload — the admin client signs and broadcasts. **Auth required:** Bearer JWT (admin) **Response 200:** `{ success: true, data: { /* tx payload */ } }` ### POST /api/payment/:id/release/confirm **Description:** Records the broadcast transaction hash for the release; marks the payment as released, updates [[PurchaseRequest]] to `seller_paid` and emits `purchase-request-update` (`type: payment_released`). **Auth required:** Bearer JWT (admin) **Request body:** `{ txHash: string }` **Errors:** `400` missing `txHash`. ### POST /api/payment/:id/refund **Description:** Mirror of release, but returns the escrow to the buyer. **Auth required:** Bearer JWT (admin) ### POST /api/payment/:id/refund/confirm **Description:** Records the refund tx hash; emits `purchase-request-update` (`type: payment_refunded`). **Auth required:** Bearer JWT (admin) **Request body:** `{ txHash: string }` ## Legacy SHKeeper - Payouts Historical payouts were SHKeeper-side outbound transfers. Current routine releases should use ledger-gated release/refund orchestration instead. ### POST /api/payment/shkeeper/payout **Description:** Create a payout task. The server creates a [[Payment]] row with `direction: "out"` and provider task id, then returns the SHKeeper task descriptor. **Auth required:** Bearer JWT (admin) **Request body:** ```ts { purchaseRequestId: string; sellerOfferId: string; buyerId: string; sellerId: string; amount: number; recipientAddress: string; token?: string; // default "USDT" network?: string; // default "bsc" metadata?: Record; } ``` **Response 200:** `{ success, data: { payoutId, taskId, status }, message }` **Side effects:** `emitGlobalEvent('payout-created', { ... })`. **Errors:** `400` for each missing required field, `500` upstream error. ### GET /api/payment/shkeeper/payout/status/:taskId **Description:** Polls SHKeeper for the current task status. **Auth required:** Bearer JWT **Response 200:** `{ success, data: { /* status payload */ } }` ### POST /api/payment/shkeeper/payout/webhook **Description:** SHKeeper webhook for payout state changes. Handled by `processPayoutWebhookEvent`. Emits `payout-completed` (or `payout-updated`) global socket events on success. **Auth required:** No (signature checked) **Response 200/400:** `{ success, message, data }` ## Legacy Web3 Wallet-Direct (DePay) > ⚠️ **NOT IMPLEMENTED:** `POST /payment/depay/intents` (`createDePayIntent`) does not exist in the codebase. ### POST /api/payment/decentralized/save **Description:** Persists a Web3-initiated payment record. **Auth required:** Bearer JWT (enforces `userId` ownership match) **Request body:** ```ts { purchaseRequestId: string; buyerId: string; sellerId?: string; amount: number; currency?: string; transactionHash?: string; network?: string; token?: string; walletAddress?: string; metadata?: Record; } ``` ### GET /api/payment/decentralized/status/:paymentId **Description:** Returns the latest status for a decentralized payment. **Auth required:** No ### PUT /api/payment/decentralized/update **Description:** Update a decentralized payment's status / confirmations. **Auth required:** Bearer JWT (owner or admin) **Request body:** `{ paymentId, status, confirmations? }` ### GET /api/payment/decentralized/receiver **Description:** Returns the configured escrow receiver wallet address. **Auth required:** No ### GET /api/payment/decentralized/history/:userId **Description:** Decentralized payment history for a user. **Auth required:** Bearer JWT (self or admin) ### POST /api/payment/decentralized/verify/:paymentId **Description:** Re-verifies a single decentralized payment against the chain. `paymentId` is a **path parameter** as shown. **Auth required:** Bearer JWT (owner or admin) ### POST /api/payment/decentralized/verify-all-pending **Description:** Iterates all `pending` decentralized payments and re-verifies them. **Auth required:** Bearer JWT (admin only) ### POST /api/payment/decentralized/admin-payout **Description:** Pay a seller directly from an admin hot wallet. This bypasses the newer ledger-gated release/refund orchestration and should not be used for routine releases. **Auth required:** Bearer JWT (admin) **Request body:** ```ts { purchaseRequestId: string; receiverWalletAddress: string; amount: number; currency?: string; // default "USDT" network?: string; // default "BSC" } ``` **Response 200:** `{ success, data: { /* payout receipt */ } }` ## Derived Destinations These endpoints manage per-(buyer, sellerOffer) ephemeral payment addresses. | Method | Route | Auth | Purpose | |--------|-------|------|---------| | `GET` | `/api/payment/derived-destinations` | Admin | List destinations with filters/pagination | | `POST` | `/api/payment/derived-destinations/sweep` | Admin | Sweep **all** active destinations | | `POST` | `/api/payment/derived-destinations/:id/sweep` | Admin | Sweep **one** destination | | `POST` | `/api/payment/derived-destinations/:id/balance` | Admin | Refresh on-chain balance for one destination | | `GET` | `/api/payment/derived-destinations/config/health` | Admin | Verify xpub and sweep signer config | | `POST` | `/api/payment/derived-destinations/cron/start` | Admin | Start the sweep cron | | `POST` | `/api/payment/derived-destinations/cron/stop` | Admin | Stop the sweep cron | | `GET` | `/api/payment/derived-destinations/cron/status` | Admin | Check if sweep cron is running | ### `GET /api/payment/derived-destinations` Query params: `buyerId`, `sellerOfferId`, `status` (`active|swept`), `address`, `chainId`, `page`, `limit`. **Response 200:** ```json { "success": true, "data": { "destinations": [ { "_id": "...", "buyerId": "...", "sellerOfferId": "...", "address": "0x...", "derivationPath": "m/44'/60'/0'/0/5", "derivationIndex": 5, "chainId": 56, "status": "active", "balance": "1000000000", "sweepCount": 0, "totalSwept": "0", "createdAt": "..." } ], "pagination": { "page": 1, "limit": 20, "total": 42 } } } ``` ### `POST /api/payment/derived-destinations/sweep` Body: `{ chainId?: number, tokenSymbol?: string, minSweepAmount?: string }` — all optional. **Response 200:** `{ success: true, data: { results: SweepResult[] } }` Each `SweepResult`: ```ts { destinationId: string; address: string; status: 'success' | 'error' | 'skipped'; txHash?: string; amount?: string; error?: string; } ``` ### `POST /api/payment/derived-destinations/:id/sweep` Same result shape as above, but for a single destination. ### `GET /api/payment/derived-destinations/config/health` **Response 200:** ```json { "success": true, "data": { "xpubValid": true, "xpubFingerprint": "0xabcd...", "signerType": "build-only", "signerHealthy": true, "chainId": 56, "masterWallet": "0x..." } } ``` ## Frontend PaymentProvider type `src/types/payment.ts` defines `PaymentProvider` as: ```ts type PaymentProvider = 'request.network' | 'test' | 'other'; ``` > ⚠️ **Type gap (M37):** Despite both SHKeeper and the legacy wallet-direct (DePay/decentralized) flows being active in production, neither `'shkeeper'` nor `'decentralized'` appears in this union. Any frontend code that branches on `provider` will treat both as `'other'` or fall through a switch default. The backend stores the literal strings `"shkeeper"` and `"decentralized"` in the database; the mismatch exists only in the frontend type definition. ## Status model [[Payment]] uses the statuses below across all providers: - `pending` - intent created, awaiting on-chain settlement - `processing` - settlement seen, awaiting confirmations - `confirmed` - fully credited (intermediate; sometimes skipped). **Note:** this is the only status counted as `successfulPayments` in `GET /api/payment/stats`. - `completed` - confirmed, escrow funded. Terminal state for SHKeeper and DePay. **Not** counted in `successfulPayments` stats — see stats undercounting note above. - `failed` - intentionally failed (expired, declined, refused) - `cancelled` - cancelled by user/admin - `released` - escrow released to seller through the release/refund orchestration and custody signer - `refunded` - escrow returned to buyer Escrow state (`escrowState`): `unfunded` → `funded` → `released` | `refunded`. ## Confirmation thresholds (admin) ### `GET /api/admin/settings/confirmation-thresholds` **Auth:** Admin only **Response 200:** ```json { "success": true, "data": [ { "chainId": 56, "threshold": 12, "source": "default" }, { "chainId": 1, "threshold": 3, "source": "config" } ] } ``` ### `PATCH /api/admin/settings/confirmation-thresholds/:chainId` **Auth:** Admin only **Body:** `{ "threshold": 3 }` **Description:** Updates the runtime confirmation threshold for a chain. The in-memory cache is invalidated immediately so the next `TransactionSafetyProvider` evaluation uses the new value. **Response 200:** ```json { "success": true, "message": "Confirmation threshold for chain 56 updated to 3", "data": { "chainId": 56, "threshold": 3 } } ``` > ⚠️ **NOT IMPLEMENTED:** `GET /api/admin/settings/confirmation-thresholds/history` does not exist. Only the current-values GET and per-chain PATCH endpoints are implemented. ## Payments awaiting confirmation (admin) ### `GET /api/admin/payments/awaiting-confirmation` **Auth:** Admin only **Query:** `page`, `limit`, `chainId` (optional) **Description:** Lists payments that have an on-chain transaction hash but have not yet reached sufficient confirmations (i.e. `metadata.transactionSafety.status === 'pending'` or `escrowState` is not funded/released/refunded). **Response 200:** ```json { "success": true, "data": [ { "_id": "...", "paymentId": "...", "status": "pending", "amount": { "amount": 12.5, "currency": "USDC" }, "blockchain": { "network": "bsc", "transactionHash": "0x...", "confirmations": 3 }, "metadata": { "transactionSafety": { "status": "pending", "checks": [...] } }, "createdAt": "2026-05-28T..." } ], "pagination": { "page": 1, "limit": 25, "total": 4, "totalPages": 1 } } ``` ## Request Network multichain registry (admin) ### `GET /api/admin/rn/networks` **Auth:** Admin only **Response 200:** ```json { "success": true, "data": [ { "chainId": 56, "name": "BNB Smart Chain", "shortName": "BSC", "rpcUrl": "https://bsc-dataseed.binance.org/", "publicRpcUrl": "https://bsc-rpc.publicnode.com", "blockExplorer": "https://bscscan.com", "proxyAddress": "0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9", "nativeCurrency": { "name": "BNB", "symbol": "BNB", "decimals": 18 }, "confirmationThreshold": 12, "tokens": [ { "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "symbol": "USDC", "decimals": 18, "name": "Binance-Peg USD Coin" }, { "address": "0x55d398326f99059ff775485246999027b3197955", "symbol": "USDT", "decimals": 18, "name": "Binance-Peg BSC-USD" } ] } ], "meta": { "chainCount": 5, "tokenCount": 10 } } ``` > ⚠️ **NOT IMPLEMENTED:** `POST /api/admin/rn/networks/reload` and `POST /api/admin/rn/networks/probe/:chainId` do not exist in the codebase. ## Related - [[Escrow Flow]] - [[Request Network Integration Constraints]] - [[Payout Flow]] - [[Socket Events]]