docs: sync from backend 19f7eb9, frontend 60ee6fb — Task #10 AML screening
This commit is contained in:
@@ -44,7 +44,8 @@ backend/src/
|
||||
│ │ ├── migration/ # Legacy data backfill utilities
|
||||
│ │ ├── observability/ # Logging and incident controls
|
||||
│ │ ├── requestNetwork/ # Request Network pay-in, routes, webhook signature
|
||||
│ │ └── shkeeper/ # SHKeeper API, webhook, payout
|
||||
│ │ ├── safety/ # Transaction Safety Provider + confirmation thresholds
|
||||
│ │ └── wallets/ # Derived destination wallets + sweep orchestration
|
||||
│ ├── points/ # Loyalty points, levels, redemption
|
||||
│ ├── redis/ # Redis client, cache helpers
|
||||
│ ├── telegram/ # Bot webhook, Mini App session, identity linking, notifications
|
||||
@@ -125,17 +126,19 @@ The full route table mounted by `app.ts`:
|
||||
| `/api/marketplace/categories` | `services/marketplace/controllerRoutes.ts` | public read | Category list |
|
||||
| `/api/marketplace/shop-settings` | `services/marketplace/shopSettingsController.ts` | JWT (seller) | Shop profile |
|
||||
| `/api/payment` | `services/payment/paymentControllerRoutes.ts` + `paymentRoutes.ts` | JWT | Payment CRUD, health, export |
|
||||
| `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Web3 save, verify, receiver |
|
||||
| `/api/payment/shkeeper` | `services/payment/shkeeper/shkeeperRoutes.ts` | mixed | Intents, webhook, release, refund, config |
|
||||
| `/api/payment/shkeeper/payout` | `services/payment/shkeeper/shkeeperPayoutRoutes.ts` | JWT (seller/admin) | Withdraw to wallet |
|
||||
| `/api/payment/request-network` | `services/payment/requestNetwork/requestNetworkRoutes.ts` | HMAC sig | Request Network pay-in creation, Secure Payment Page, webhooks |
|
||||
| `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Legacy/manual Web3 save, verify, receiver |
|
||||
| `/api/payment/request-network` | `services/payment/requestNetwork/requestNetworkRoutes.ts` | mixed + HMAC sig on webhook | Request Network pay-in creation, in-house checkout rehydrate, webhooks |
|
||||
| `/api/payment/derived-destinations` | `services/payment/wallets/derivedDestinationRoutes.ts` | JWT (admin) | Derived address list, sweeps, cron, config health |
|
||||
| `/api/admin/rn/networks` | `services/payment/requestNetwork/networkRegistryRoutes.ts` | JWT (admin) | Supported RN chain/token registry |
|
||||
| `/api/admin/settings/confirmation-thresholds` | `services/admin/confirmationThresholdRoutes.ts` | JWT (admin) | Runtime min-confirmation thresholds |
|
||||
| `/api/admin/payments/awaiting-confirmation` | `services/admin/awaitingConfirmationRoutes.ts` | JWT (admin) | Payments blocked on safety confirmations |
|
||||
| `/api/telegram` | `services/telegram/telegramRoutes.ts` | mixed (some JWT, webhook uses secret-token) | Mini App verify/session, identity link/unlink, bot webhook |
|
||||
| `/api/chat` | `services/chat/chatRoutes.ts` | JWT | Conversations, messages |
|
||||
| `/api/notification` | `services/notification/notificationRoutes.ts` + `notificationControllerRouter` | JWT | List, mark read |
|
||||
| `/api/dispute` | `services/dispute/disputeRoutes.ts` | JWT | **Not implemented** — planned |
|
||||
| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | **Not implemented** — planned |
|
||||
| `/api/admin` | `services/admin/adminRoutes.ts` | JWT (admin) | **Not implemented** — planned |
|
||||
| `/api/points` | `services/points/pointsRoutes.ts` | JWT | **Not implemented** — planned |
|
||||
| `/api/disputes` | `routes/disputeRoutes.ts` + `services/dispute/disputeRoutes.ts` | JWT | Dispute CRUD plus release-hold helpers |
|
||||
| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | Public reads, admin writes |
|
||||
| `/api/admin/cleanup` | `services/admin/dataCleanupRoutes.ts` | JWT (admin) | Data cleanup operations |
|
||||
| `/api/points` | `services/points/pointsRoutes.ts` | JWT | Points, levels, referrals |
|
||||
| `/api/ai` | `services/ai/aiRoutes.ts` | JWT | OpenAI-backed helpers |
|
||||
| `/api/files` | `services/file/fileRoutes.ts` | JWT | Multipart upload |
|
||||
| `/api/email` | `services/email/emailRoutes.ts` | JWT | Email dispatch |
|
||||
@@ -253,9 +256,12 @@ Full table in [[Environment Variables]]. Critical ones:
|
||||
| `JWT_EXPIRES_IN` | `7d` | |
|
||||
| `REFRESH_TOKEN_EXPIRES_IN` | `30d` | |
|
||||
| `FRONTEND_URL` | `http://localhost:3000` | CORS origin |
|
||||
| `SHKEEPER_API_URL` | `https://pay.amn.gg` | |
|
||||
| `SHKEEPER_API_KEY` | required | |
|
||||
| `SHKEEPER_WEBHOOK_SECRET` | required | HMAC key |
|
||||
| `REQUEST_NETWORK_API_BASE_URL` | `https://api.request.network` | Request Network API |
|
||||
| `REQUEST_NETWORK_API_KEY` | required | Request Network API credential |
|
||||
| `REQUEST_NETWORK_WEBHOOK_SECRET` | required | Webhook HMAC key |
|
||||
| `PAYMENT_LEDGER_ENFORCEMENT` | `false` | Target `true` before launch-scale releases |
|
||||
| `TRANSACTION_SAFETY_*` | required for payments | Confirmation, transfer-match, and AML controls |
|
||||
| `DERIVED_DESTINATION_SWEEP_SIGNER` | `build-only` | Target hardware/Safe-backed signer |
|
||||
| `SMTP_*` | required | Nodemailer |
|
||||
| `OPENAI_API_KEY` | required | |
|
||||
|
||||
@@ -279,7 +285,7 @@ Redis client (in `src/services/redis/`) provides:
|
||||
|
||||
The codebase has no dedicated queue runner — scheduled / async work is triggered inline from request handlers and uses `setTimeout` / `setInterval` patterns where needed (e.g., delayed retries). Consider introducing Bull / BullMQ if you grow:
|
||||
|
||||
- Payment status reconciliation (polling SHKeeper for stragglers)
|
||||
- Request Network webhook replay/reconciliation and derived-destination balance checks
|
||||
- Notification email digests
|
||||
- Auto-release escrow timers
|
||||
- Token / refresh-token cleanup
|
||||
@@ -295,7 +301,7 @@ Jest test suites in `backend/__tests__/`:
|
||||
| `models.test.ts` | Schema validation, virtuals, hooks |
|
||||
| `payment-services.test.ts` | Payment orchestration logic |
|
||||
| `complete-backend.test.ts` | Cross-service integration |
|
||||
| `shkeeper-backend.test.ts` | SHKeeper service + webhook |
|
||||
| Request Network / payment tests | Request Network adapter, webhook signature, ledger, release/refund orchestration |
|
||||
|
||||
Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`, `npm run test:payment`, etc. when iterating on a slice.
|
||||
|
||||
@@ -310,7 +316,8 @@ Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`,
|
||||
| `src/shared/utils/response-handler.ts` | Standard response shape |
|
||||
| `src/shared/middleware/auth.ts` | JWT verify + RBAC |
|
||||
| `src/infrastructure/socket/socketService.ts` | All socket plumbing |
|
||||
| `src/services/payment/shkeeper/shkeeperWebhook.ts` | Webhook signature scheme |
|
||||
| `src/services/payment/requestNetwork/requestNetworkRoutes.ts` | Request Network checkout and webhook route |
|
||||
| `src/services/payment/ledger/fundsLedgerService.ts` | Immutable payment ledger writes |
|
||||
| `src/services/marketplace/PurchaseRequestService.ts` | Core marketplace state machine |
|
||||
| `src/services/auth/authService.ts` | Auth flows, lockout, hashing |
|
||||
| `src/models/User.ts` | Central entity with role/preferences |
|
||||
@@ -325,4 +332,4 @@ Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`,
|
||||
- [[Real-time Layer]] — Socket.IO room model
|
||||
- [[Security Architecture]] — JWT, passkeys, webhook HMAC
|
||||
- [[Data Model Overview]] — entity-relationship map
|
||||
- [[Authentication Flow]] · [[Payment Flow - SHKeeper]] · [[Dispute Flow]]
|
||||
- [[Authentication Flow]] · [[Escrow Flow]] · [[Dispute Flow]]
|
||||
|
||||
@@ -190,7 +190,7 @@ See [[Monitoring]] for the full table of metrics & recommended alerts.
|
||||
| Browser → Backend | 5001 | HTTP + WS | via Nginx `/api`, `/socket.io` |
|
||||
| Backend → MongoDB | 27017 | TCP | Docker network |
|
||||
| Backend → Redis | 6379 | TCP | Docker network |
|
||||
| Backend → SHKeeper | 443 | HTTPS | External |
|
||||
| Backend → Request Network API | 443 | HTTPS | External payment provider |
|
||||
| Backend → SMTP | 587 | TLS | External |
|
||||
| Backend → OpenAI | 443 | HTTPS | External |
|
||||
| Browser → Blockchain RPC | 443 | HTTPS | Alchemy URLs |
|
||||
|
||||
@@ -215,6 +215,6 @@ Sticky sessions on the load balancer are also required so a given client always
|
||||
## Related
|
||||
|
||||
- [[Backend Architecture]] · [[Frontend Architecture]]
|
||||
- [[Chat Flow]] · [[Notification Flow]] · [[Payment Flow - SHKeeper]] · [[Dispute Flow]]
|
||||
- [[Chat Flow]] · [[Notification Flow]] · [[Escrow Flow]] · [[Dispute Flow]]
|
||||
- [[Security Architecture]] — socket auth concerns
|
||||
- [[Socket Events]] — full event reference (developer-facing API doc)
|
||||
|
||||
@@ -8,15 +8,15 @@ This document captures payment-flow issues that surfaced while integrating Reque
|
||||
|
||||
---
|
||||
|
||||
## 1. RN does not support Rabby — show-stopper for our wallet user base
|
||||
## 1. RN hosted UI does not support Rabby -- mitigated by Amanat in-house checkout
|
||||
|
||||
### Problem
|
||||
|
||||
RN's hosted payment page (the `pay.request.network/?token=…` UI returned by `/v2/secure-payments`) does not detect / connect to Rabby. A meaningful slice of Amanat's user base pays from Rabby. Sending them to a screen that won't even let them connect is a hard block.
|
||||
|
||||
### Mitigation (designed, not yet implemented)
|
||||
### Mitigation (implemented core path)
|
||||
|
||||
Skip the RN-hosted UI. We already call `/v2/secure-payments` and receive a `securePaymentUrl`, but we also receive `requestIds` and `token` — that's everything we need to know what the merchant request is. Behind that token there is a contract on the destination chain that anyone can fulfill.
|
||||
Skip the RN-hosted UI. Amanat still calls `/v2/secure-payments`, stores the Request Network identifiers, and exposes an in-house checkout block. The frontend builds the same RN-compatible on-chain action from the buyer's wallet, so Rabby/MetaMask users stay inside the Amanat flow.
|
||||
|
||||
So the new flow becomes:
|
||||
|
||||
@@ -32,10 +32,11 @@ So the new flow becomes:
|
||||
- RN's value to us at that point is the *settlement bookkeeping*, not the UI. We use them as "did this address receive the expected amount before timeout?" — the wallet UX stays in our control.
|
||||
- Buyer never sees a third-party brand mid-checkout, which is a UX win regardless of Rabby.
|
||||
|
||||
### Open
|
||||
### Remaining work
|
||||
|
||||
- Need to confirm RN settles a payment that arrives from a *proxy transaction we built*, not from their hosted page. The 2026-05-28 probe confirms RN webhook delivery to Amanat, but the app returned `404`; repeat the probe only after the confirmation repair is deployed.
|
||||
- Need a fallback for the buyer who insists on the RN hosted UI (some users will already have the link copied). Keep `securePaymentUrl` exposed as a "advanced / pay with RN" link.
|
||||
- Keep the RN hosted URL exposed as an escape hatch.
|
||||
- Continue hardening timer/persistence/telemetry around the in-house checkout.
|
||||
- Treat durable webhook ingress as a production gate, because the main Express app should not be the only landing zone for callback evidence.
|
||||
|
||||
---
|
||||
|
||||
@@ -51,7 +52,7 @@ The visible costs:
|
||||
- Or seller gets less than they expected (worst — they'll dispute).
|
||||
- Plus settlement latency goes from seconds to minutes-hours depending on the bridge.
|
||||
|
||||
### Mitigation (designed)
|
||||
### Mitigation (partially implemented)
|
||||
|
||||
Take the chain choice away from RN's UI and bring it into ours, gated by what the *seller* will accept.
|
||||
|
||||
@@ -62,11 +63,11 @@ Two-step UX:
|
||||
|
||||
### Side benefit
|
||||
|
||||
This composes cleanly with #1 (own checkout screen): we already have to render the wallet picker, so adding a chain selector before the wallet step costs almost nothing.
|
||||
This composes cleanly with #1 (own checkout screen): we already render the wallet picker, so seller-accepted chain selection can happen before wallet connection. The chain/token registry and admin networks page exist; seller-side accepted-chain policy remains a separate product/data-model task.
|
||||
|
||||
### Open
|
||||
|
||||
- We need a per-seller config table for accepted chains. Today the env-level `REQUEST_NETWORK_MERCHANT_REFERENCE` hard-codes a single chain (`bsc`). Needs to become per-seller, per-offer.
|
||||
- We need a per-seller/per-offer config table for accepted chains. Today the global merchant reference is still the fallback, while derived destination work handles recipient variation.
|
||||
- Does RN's API support creating a secure-payment that *rejects* off-chain payments rather than auto-bridging? Or do we have to enforce this purely on our side by never offering the cross-chain option to the buyer? **Confirm with RN docs/support.**
|
||||
|
||||
---
|
||||
@@ -83,7 +84,7 @@ Today the entire escrow stack receives funds into one (or a handful of) wallets
|
||||
|
||||
This is a show-stopper for going live at scale. Same class of issue we already considered around SHKeeper.
|
||||
|
||||
### Mitigation (designed; needs RN feasibility check)
|
||||
### Mitigation (implemented core path; operational probe pending)
|
||||
|
||||
Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-generated address that only ever receives that one transaction. If those funds turn out to be dirty:
|
||||
|
||||
@@ -93,23 +94,23 @@ Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-g
|
||||
|
||||
### What this requires (architectural work)
|
||||
|
||||
1. **Wallet abstraction layer** — service that on demand generates a fresh address (HD wallet derivation from a master seed kept in a hardware module / KMS) and returns it to the payment-intent flow.
|
||||
2. **Address book / registry** — maps `(paymentId, chainId)` → derived address. Persists derivation path + sequence number so we can reproduce keys for sweeps later.
|
||||
3. **Sweep job** — once a payment is confirmed AND has passed an on-chain screening check (Chainalysis API or similar), sweep the ephemeral wallet to the main treasury. If screening fails, the ephemeral wallet is quarantined and the payment refunded out of band.
|
||||
4. **Key custody policy** — these are still our funds in custody briefly; need clear policy on who can sign sweeps, hot-key vs cold-key separation.
|
||||
1. **Wallet abstraction layer** -- implemented in `backend/src/services/payment/wallets/derivedDestinations.ts` using xpub-only derivation.
|
||||
2. **Address book / registry** -- implemented in `DerivedDestination`, keyed by `(buyerId, sellerOfferId, chainId)`.
|
||||
3. **Sweep job** -- implemented with build-only/hot-key signer abstraction; production must keep build-only and move execution to Trezor/Safe.
|
||||
4. **Key custody policy** -- still the important missing operational layer. See [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]].
|
||||
|
||||
### Critical open question
|
||||
|
||||
**Does RN support creating a secure-payment with a destination wallet we specify per-request, rather than a static merchant reference?** If yes, this is straightforward — we generate a wallet, register it as the destination for one specific `/v2/secure-payments` call, done. If no (RN only allows pre-registered destinations), we have to either:
|
||||
**Does RN support creating a secure-payment with a destination wallet we specify per request at production volume, rather than a static merchant reference?** The backend/frontend support the shape, but the live divergent-destination probe remains the operational proof point. If RN cannot support this reliably, fallback options are:
|
||||
|
||||
- Pre-register a large pool of addresses with RN and rotate through them, or
|
||||
- Bypass RN's destination model and go full self-host (which is most of issue #4).
|
||||
|
||||
**Action: confirm with RN support whether per-request destinations are supported on the same API key.**
|
||||
**Action: run the two-paid-intent divergent-destination probe and confirm with RN support whether this usage is supported on the same API key at expected volume.**
|
||||
|
||||
---
|
||||
|
||||
## 4. RN reduced to a notification service — viable, but not yet validated
|
||||
## 4. RN reduced to a notification service -- viable, partially validated
|
||||
|
||||
### Problem statement
|
||||
|
||||
@@ -131,19 +132,19 @@ Which is a *notification* primitive, not a payment platform. We'd be paying for
|
||||
- We're outsourcing the *one thing* RN is good at (settlement) and keeping the parts they don't help with (UX, wallet generation, compliance).
|
||||
- Alternative: do the same with our own chain watcher (Alchemy webhooks / Tenderly / Goldsky) and skip RN entirely.
|
||||
|
||||
### What needs testing before we commit
|
||||
### What still needs testing before we commit at scale
|
||||
|
||||
1. **Webhook reliability at our volume.** What's RN's SLA for "address received funds → webhook delivered"? P50? P99?
|
||||
2. **Custom destination support.** See open question in #3.
|
||||
3. **Per-API-key rate limits.** If we end up calling `/v2/secure-payments` once per escrow, do we hit ceilings?
|
||||
4. **Pricing for the notification-only flow** — is there a tier, or is it the same as the full-stack price?
|
||||
5. **What happens when the payment arrives from a transaction WE built** (not theirs)? Does the webhook still fire? Is settlement still recognized? — this is the load-bearing test for the whole strategy.
|
||||
5. **What happens when the payment arrives from a transaction WE built** (not theirs)? The 2026-05-28 in-house checkout probe proved the basic path for a real BSC USDC payment; this still needs repeated paid probes across tokens/chains and webhook durability coverage.
|
||||
|
||||
Until #5 is confirmed, the rest is just paper architecture.
|
||||
Until webhook durability, destination divergence, pricing, and SLA are confirmed, treat RN as useful but not irreplaceable infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## 5. Webhook durability and transaction safety are P0 before more paid probes
|
||||
## 5. Webhook durability remains P0 before production rollout
|
||||
|
||||
### What the 2026-05-28 probe proved
|
||||
|
||||
@@ -153,12 +154,12 @@ The dev test transaction `0x3a23febd9abd43d7e0851c1ea86c4ceaf08c11098852cb0425fa
|
||||
|
||||
Do not treat the main Express app as the only webhook landing zone, and do not treat a signed provider callback as enough to credit escrow.
|
||||
|
||||
### Required mitigation
|
||||
### Required mitigation and status
|
||||
|
||||
1. **Correlation repair:** lookup Request Network payments by every persisted reference shape, including `providerPaymentId`, top-level RN request id/payment reference, and nested raw RN data.
|
||||
2. **Callback repair:** payment callback polling must unwrap the backend response shape, clear polling after terminal states, and avoid a 3-second loop that self-rate-limits.
|
||||
3. **Transaction Safety Provider:** completion must pass configured safety checks: transaction hash present, minimum confirmations, token/recipient/amount transfer match, and future AML/sanctions provider approval.
|
||||
4. **Durable ingress:** put a Cloudflare Worker in front of RN webhooks. The Worker stores raw delivery evidence durably, forwards to the backend, and supports replay. It is not the trust oracle; the backend still verifies, deduplicates, and applies safety/ledger transitions.
|
||||
1. **Correlation repair:** implemented for the in-house checkout path; keep smoke coverage around every persisted RN reference shape.
|
||||
2. **Callback repair:** implemented enough for the successful paid dev probe; keep polling/backoff hardening on the checkout roadmap.
|
||||
3. **Transaction Safety Provider:** implemented for tx hash, confirmations, transfer match, and AML placeholder; real AML provider remains Task #10.
|
||||
4. **Durable ingress:** not started. Put a Cloudflare Worker in front of RN webhooks. The Worker stores raw delivery evidence durably, forwards to the backend, and supports replay. It is not the trust oracle; the backend still verifies, deduplicates, and applies safety/ledger transitions.
|
||||
|
||||
---
|
||||
|
||||
@@ -166,13 +167,13 @@ Do not treat the main Express app as the only webhook landing zone, and do not t
|
||||
|
||||
| # | Action | Blocker / Owner |
|
||||
|---|---|---|
|
||||
| 1 | Deploy confirmation repair and repeat the dev payment probe | Backend payments |
|
||||
| 2 | Test: `/v2/secure-payments` accepts a per-request destination wallet | Backend payments |
|
||||
| 1 | Run the live divergent-destination probe: two paid intents to two derived addresses | Backend payments |
|
||||
| 2 | Confirm `/v2/secure-payments` per-request destination usage with RN support and pricing | Product / RN account manager |
|
||||
| 3 | Confirm RN doesn't auto-bridge when buyer pays on the destination chain natively | Backend payments |
|
||||
| 4 | Get RN's webhook P99 latency + delivery guarantees in writing | Product / RN account manager |
|
||||
| 5 | Spec the wallet-abstraction layer (HD derivation + sweep job + key policy) | Backend, before going live |
|
||||
| 5 | Move sweep/release/refund custody to Trezor/Safe, not backend hot keys | Backend + ops |
|
||||
| 6 | Spec the seller-side accepted-chains config | Backend + frontend |
|
||||
| 7 | Add Cloudflare Worker durable webhook ingress to the roadmap | Backend / platform |
|
||||
| 7 | Build Cloudflare Worker durable webhook ingress + replay | Backend / platform |
|
||||
| 8 | Add AML/sanctions adapter behind Transaction Safety Provider | Compliance / backend |
|
||||
|
||||
Actions 1–4 are *information-gathering* and should run in parallel before any more architectural commitment to RN. Actions 5–6 are blocked on 1–3 confirming RN can actually support this shape.
|
||||
Actions 1-4 are information-gathering and should run in parallel before deeper RN commitment. Actions 5, 7, and 8 are production-safety work regardless of whether Amanat keeps RN long-term or replaces it with a direct chain watcher.
|
||||
|
||||
@@ -9,7 +9,7 @@ created: 2026-05-23
|
||||
How identity, authorization, transport, and integrity are handled across the platform.
|
||||
|
||||
> [!important]
|
||||
> Read alongside [[Authentication Flow]] (user-facing), [[Passkey (WebAuthn) Flow]], and [[Payment Flow - SHKeeper]] (webhook HMAC).
|
||||
> Read alongside [[Authentication Flow]] (user-facing), [[Passkey (WebAuthn) Flow]], [[Escrow Flow]], and [[Request Network Integration Constraints]].
|
||||
|
||||
---
|
||||
|
||||
@@ -22,7 +22,7 @@ How identity, authorization, transport, and integrity are handled across the pla
|
||||
| CSRF | JWT in `Authorization` header (not cookie), CORS allow-list |
|
||||
| XSS | Helmet CSP, React auto-escaping, sanitize HTML before storage |
|
||||
| SQL/NoSQL injection | Mongoose parameterized queries, no `$where` strings, schema validation |
|
||||
| Webhook spoofing | HMAC SHA-256 over body + secret (SHKeeper, Request Network, Telegram), constant-time compare |
|
||||
| Webhook spoofing | HMAC SHA-256 over raw body + provider secret (Request Network, Telegram), constant-time compare |
|
||||
| File upload abuse | Multer MIME validation, 5 MB cap, non-executable storage, served by Nginx not Node |
|
||||
| Replay attacks | Per-payment idempotency on `providerPaymentId`; Telegram initData in-memory replay map; per-request `X-Request-Id` |
|
||||
| Account takeover | Email verification required, password reset code expiry (1h), passkey support |
|
||||
@@ -155,34 +155,36 @@ A single User may be `buyer` and `seller` simultaneously (combined role).
|
||||
|
||||
## 5. Webhook integrity
|
||||
|
||||
### 5.1 SHKeeper
|
||||
### 5.1 Request Network
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant SHK
|
||||
participant RN
|
||||
participant WK as Durable ingress (roadmap)
|
||||
participant BE
|
||||
SHK->>BE: POST /api/payment/shkeeper/webhook<br/>X-Signature: sha256=<hmac>
|
||||
BE->>BE: hmac = HMAC_SHA256(SHKEEPER_WEBHOOK_SECRET, body)
|
||||
BE->>BE: crypto.timingSafeEqual(hmac, providedSig)
|
||||
RN->>WK: POST /api/payment/request-network/webhook<br/>x-request-network-signature
|
||||
WK->>WK: Store raw body + headers + delivery id
|
||||
WK->>BE: Forward / replay raw webhook
|
||||
BE->>BE: verifyRequestNetworkWebhookSignature(rawBody, headers)
|
||||
alt mismatch
|
||||
BE-->>SHK: 401 Unauthorized
|
||||
BE-->>WK: 401 Unauthorized
|
||||
else match
|
||||
BE->>BE: process payment update
|
||||
BE-->>SHK: 200 OK
|
||||
BE->>BE: idempotency + Transaction Safety Provider
|
||||
BE->>BE: process payment update / ledger entry
|
||||
BE-->>WK: 200 OK
|
||||
end
|
||||
```
|
||||
|
||||
- Raw body must be used for HMAC — `express.raw({ type: 'application/json' })` is mounted on this route only (before the global `express.json()` parser).
|
||||
- In dev (`NODE_ENV === 'development'`) signature verification can be bypassed for local testing — confirm this is gated and never reachable in prod.
|
||||
- Idempotency: identical webhook delivered twice should be no-op. Check by `(providerPaymentId, status)` tuple before mutating.
|
||||
|
||||
### 5.2 Request Network
|
||||
|
||||
- Webhooks arrive at `/api/payment/request-network/webhook` with an `x-request-network-signature` header.
|
||||
- The backend verifies the signature using `backend/src/services/payment/requestNetwork/signature.ts` before any state mutation.
|
||||
- The route is mounted **before** the global `express.json()` body parser so raw body bytes are available for signature computation.
|
||||
- The global rate-limit middleware is configured to skip this path to avoid blocking high-frequency payment events.
|
||||
- Reconciliation service (`requestNetworkReconciliationService.ts`) handles replayed or out-of-order webhooks idempotently.
|
||||
- Durable ingress is the target production shape: the Worker stores delivery evidence and supports replay, but the backend remains the trust oracle.
|
||||
|
||||
### 5.2 Legacy SHKeeper note
|
||||
|
||||
SHKeeper-specific webhook docs are historical migration context. The current backend payment tree uses Request Network as the primary provider; do not reintroduce SHKeeper signature bypasses or fallback webhook heuristics without a new security review.
|
||||
|
||||
### 5.3 Telegram Bot webhook
|
||||
|
||||
@@ -191,7 +193,7 @@ sequenceDiagram
|
||||
- A per-update-id in-memory replay map prevents duplicate processing within the configured window.
|
||||
- The global rate-limit middleware is configured to skip this path.
|
||||
|
||||
See [[Payment Flow - SHKeeper]] for the SHKeeper full flow.
|
||||
See [[Escrow Flow]] and [[Request Network Integration Constraints]] for the current payment path.
|
||||
|
||||
---
|
||||
|
||||
@@ -219,7 +221,7 @@ See [[Payment Flow - SHKeeper]] for the SHKeeper full flow.
|
||||
- Never log secrets — logger redaction recommended (winston/pino formatter).
|
||||
- `.env*` files in `.gitignore`. Repo includes only `.env.development` / `.env.production` templates with **public** values (NEXT_PUBLIC_*).
|
||||
- Rotate `JWT_SECRET` invalidates all existing JWTs — schedule a maintenance window.
|
||||
- Rotate `SHKEEPER_WEBHOOK_SECRET` coordinated with SHKeeper dashboard (set new → verify → remove old).
|
||||
- Rotate `REQUEST_NETWORK_WEBHOOK_SECRET` coordinated with Request Network configuration (set new → verify → remove old).
|
||||
|
||||
See [[Environment Variables]] for the catalog.
|
||||
|
||||
@@ -277,6 +279,6 @@ The codebase currently uses `morgan` (HTTP access logs) and ad-hoc `logger.info/
|
||||
|
||||
- [[Authentication Flow]] (includes Telegram first-class auth flow) · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]]
|
||||
- [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]]
|
||||
- [[Payment Flow - SHKeeper]] — webhook HMAC details
|
||||
- [[Request Network Integration Constraints]] — payment webhook, checkout, and reconciliation constraints
|
||||
- [[Environment Variables]] — secret catalog
|
||||
- [[Incident Response]] — what to do when something goes wrong
|
||||
|
||||
@@ -24,7 +24,8 @@ flowchart LR
|
||||
BE[Express Backend<br/>+ Socket.IO<br/>:5001]
|
||||
Mongo[(MongoDB 8)]
|
||||
Redis[(Redis 8)]
|
||||
SHK[SHKeeper<br/>Crypto Gateway]
|
||||
RN[Request Network<br/>Pay-in + webhooks]
|
||||
CFWorker[Durable webhook ingress<br/>roadmap]
|
||||
SMTP[SMTP<br/>Nodemailer]
|
||||
OAI[OpenAI API]
|
||||
BC[Blockchain RPC<br/>Alchemy / WalletConnect]
|
||||
@@ -37,8 +38,9 @@ flowchart LR
|
||||
FE -.->|Socket.IO| BE
|
||||
BE --> Mongo
|
||||
BE --> Redis
|
||||
BE -->|Pay-in / Pay-out| SHK
|
||||
SHK -.->|Webhook HMAC| BE
|
||||
BE -->|Pay-in intent / status| RN
|
||||
RN -.->|Signed webhook| CFWorker
|
||||
CFWorker -.->|Forward / replay| BE
|
||||
BE --> SMTP
|
||||
BE --> OAI
|
||||
FE -->|Wallet Connect| BC
|
||||
@@ -142,25 +144,29 @@ Mutations follow optimistic-then-confirm:
|
||||
|
||||
### 5.3 Webhook path (inbound)
|
||||
|
||||
External services (SHKeeper) POST to `/api/payment/shkeeper/webhook`. The backend verifies HMAC signature, updates the `Payment` document, advances any linked `PurchaseRequest`/`SellerOffer` state, and emits Socket.IO events to both buyer and seller rooms.
|
||||
External services POST payment callbacks to provider-specific webhook routes. The current primary path is Request Network at `/api/payment/request-network/webhook`; the target architecture puts a durable ingress worker in front of the backend so raw delivery evidence can be replayed after outages. The backend remains the trust oracle: it verifies signatures, deduplicates deliveries, applies Transaction Safety Provider checks, updates ledger/payment state, and emits Socket.IO events to both buyer and seller rooms.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant SHK as SHKeeper
|
||||
participant RN as Request Network
|
||||
participant WK as Durable ingress worker
|
||||
participant BE as Backend
|
||||
participant DB as MongoDB
|
||||
participant Buyer
|
||||
participant Seller
|
||||
SHK->>BE: POST /api/payment/shkeeper/webhook<br/>X-Signature: HMAC-SHA256
|
||||
BE->>BE: verifySignature(body, header, SHKEEPER_WEBHOOK_SECRET)
|
||||
BE->>DB: Payment.updateOne({providerPaymentId}, {status:"completed"})
|
||||
BE->>DB: PurchaseRequest.updateOne(..., {status:"funded"})
|
||||
RN->>WK: POST signed webhook<br/>delivery id + raw body
|
||||
WK->>WK: Store immutable delivery evidence
|
||||
WK->>BE: Forward / replay webhook
|
||||
BE->>BE: Verify RN signature + idempotency
|
||||
BE->>BE: Transaction Safety Provider checks tx hash, recipient, token, amount, confirmations
|
||||
BE->>DB: Append ledger entry + Payment escrowState="funded"
|
||||
BE->>DB: PurchaseRequest.updateOne(..., {status:"payment"})
|
||||
BE-->>Buyer: socket emit "payment:status-updated"
|
||||
BE-->>Seller: socket emit "request:funded"
|
||||
BE-->>SHK: 200 OK
|
||||
BE-->>WK: 200 OK
|
||||
```
|
||||
|
||||
See [[Payment Flow - SHKeeper]] for the full sequence.
|
||||
See [[PRD - Request Network In-House Checkout]] and [[Request Network Integration Constraints]] for the full Request Network sequence.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user