diff --git a/docs/csp-apache.conf.md b/docs/csp-apache.conf.md
index b92563d0..75130ab0 100644
--- a/docs/csp-apache.conf.md
+++ b/docs/csp-apache.conf.md
@@ -6,6 +6,8 @@ Adjust `report-uri` if your API base path or host differs (UAT example below; PR
See also:
+- [spa-apache-routing-follow-up.md](./spa-apache-routing-follow-up.md) — SPA deep links (`/paymentPage/callback`, etc.) and Apache reverse-proxy setup
+- [csp-api-logout-404-follow-up.md](./csp-api-logout-404-follow-up.md) — keep `/api/*` off SPA `index.html` fallback
- [permissions-policy-apache.conf.md](./permissions-policy-apache.conf.md) — Permissions-Policy reporting via the same `/api/csp-report` URL
- [csp-report-review-2026-09-22.md](./csp-report-review-2026-09-22.md) — latest PROD report review (`All_CSP.txt`)
- [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — earlier PROD + UAT report review and add/remove summary
diff --git a/docs/spa-apache-routing-follow-up.md b/docs/spa-apache-routing-follow-up.md
new file mode 100644
index 00000000..d6efba58
--- /dev/null
+++ b/docs/spa-apache-routing-follow-up.md
@@ -0,0 +1,374 @@
+# Follow-up: SPA routing on Apache (`/paymentPage/callback` and deep links)
+
+Payment gateway redirects and bookmarked URLs hit paths like
+`/paymentPage/callback?TRANSACTION_ID=...&RECEIPT_TOKEN=...`. In Chrome
+DevTools, the **document** request often shows **404 Not Found**, while
+the payment callback logic still runs (console logs from
+`PaymentCallback.js`, success/cancel/fail UI).
+
+This document explains why that happens, how it relates to the current
+Apache vhost (reverse proxy to `192.168.8.x:8080`), and what to change for
+a proper SPA setup (document **200** + unchanged browser URL and query
+string).
+
+Related docs:
+
+- [csp-apache.conf.md](./csp-apache.conf.md) — CSP headers on Apache
+- [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — noted document `status-code: 404` on SPA shell (including `/paymentPage/callback`)
+- [csp-api-logout-404-follow-up.md](./csp-api-logout-404-follow-up.md) — `/api/*` must not be rewritten to `index.html`
+
+---
+
+## Symptom
+
+| Observation | Typical meaning |
+|---|---|
+| `GET /paymentPage/callback?...` → **404** | Origin (or Apache) has no file at that path |
+| Response `Server: GLD Server B`, `Content-Type: text/html` | Apache / front tier answered |
+| Console still logs transaction data | React loaded (often via `index.html` body with wrong status) |
+| Network shows red 404 on the **document** | Cosmetic for users if the shell loads; bad for monitoring, SEO, and some gateways |
+
+Example URL (card payment return from GCIS):
+
+```text
+https://pnsps.gld.gov.hk/paymentPage/callback?TRANSACTION_ID=...&RECEIPT_TOKEN=...
+```
+
+---
+
+## This is not a missing React route
+
+The client route exists and is wired to the callback page:
+
+| Piece | Location |
+|---|---|
+| Route `paymentPage/callback` | `src/routes/PublicUserRoutes.js` |
+| Component | `src/pages/Payment/PaymentCallback.js` |
+| Card `returnurl` | `src/pages/Payment/Card/index.js` (`/paymentPage/callback`) |
+
+React Router only runs **after** the browser loads the SPA shell
+(`index.html` + JS). The **first** request is a full-page **GET** from
+the payment gateway (or user bookmark). Apache / Tomcat must serve the
+shell for that path; the router never sees a request that never delivered
+HTML.
+
+Real callback work is **POST** to the backend after the page loads:
+
+| Step | API (via `ApiPathConst.js`) |
+|---|---|
+| 1 | `POST /api/payment/api/updatepayment` (`UPDATE_PAYMENT_API`) |
+| 2 | `POST /api/payment/callback/status` (`PAYMENT_CALLBACK_STATUS_API`) |
+
+Backend (`PNSPS-backend`): `PaymentController` — `/api/updatepayment`,
+`/callback/status` under the payment module (full path includes
+`server.servlet.contextPath: /api` and controller prefix).
+
+---
+
+## Current Apache pattern (DEV/UAT example)
+
+Typical vhost (`httpd-vhosts.conf`) for `pnspsdev.gld.gov.hk`:
+
+```apache
+ProxyPass /paymentrecon-dept_web/ http://192.168.8.7:18080/paymentrecon-dept_web/
+ProxyPass /paymentrecon-portal_web/ http://192.168.8.7:8080/paymentrecon-portal_web/
+ProxyPass /api/ http://192.168.8.6:8080/api/
+ProxyPass / http://192.168.8.6:8080/
+ProxyPassReverse / http://192.168.8.6:8080/
+```
+
+With only this:
+
+1. `GET /paymentPage/callback` is forwarded to `192.168.8.6:8080` as the
+ same path.
+2. There is no file at `/paymentPage/callback` on the origin.
+3. The origin returns **404** (sometimes still returning `index.html` in
+ the body — “SPA shell 404”).
+4. React may boot, read `window.location.search`, and complete payment.
+
+So: **logic can work while the document status stays 404.**
+
+Spring Boot in this repo is **API-only** (`contextPath: /api`, static
+resource mappings disabled in `application.yml`). The React build is
+served separately (commonly Tomcat or static root on `:8080`). SPA
+fallback must be configured on **Apache** and/or that static tier — not
+in Spring MVC alone.
+
+---
+
+## Design rules (all options)
+
+1. **`/api/`** — Always proxy to the backend. Never rewrite API paths to
+ `index.html`. (See [csp-api-logout-404-follow-up.md](./csp-api-logout-404-follow-up.md).)
+2. **Other proxied apps** — Keep more specific `ProxyPass` prefixes
+ (`/paymentrecon-dept_web/`, `/paymentrecon-portal_web/`, etc.) **before**
+ the catch-all `/`.
+3. **Deep links** — For `GET` requests that are not files and not API,
+ serve `index.html` with HTTP **200**.
+4. **Query string** — Payment callback needs `?TRANSACTION_ID=...&RECEIPT_TOKEN=...`
+ in the **browser** URL. Use internal rewrite to `index.html`, not an
+ external redirect that drops params.
+5. **Avoid** `ErrorDocument 404 /index.html` as the primary SPA strategy:
+ body may be the app while status remains **404** (same noise as today).
+
+---
+
+## Option A (recommended): Apache serves the React build
+
+Deploy `npm run build` output to the Apache host. Proxy only API (and
+other backends). Remove catch-all `ProxyPass /` to the app server for
+the main site.
+
+```apache
+
+ ServerName pnspsdev.gld.gov.hk
+ # ... SSLEngine, certificates ...
+
+ DocumentRoot /var/www/pnsps/build
+
+
+ Options -Indexes +FollowSymLinks
+ AllowOverride None
+ Require all granted
+ # Apache 2.4.16+ — unknown paths → index.html, status 200
+ FallbackResource /index.html
+
+
+ ProxyPreserveHost On
+
+ ProxyPass /paymentrecon-dept_web/ http://192.168.8.7:18080/paymentrecon-dept_web/
+ ProxyPassReverse /paymentrecon-dept_web/ http://192.168.8.7:18080/paymentrecon-dept_web/
+
+ ProxyPass /paymentrecon-portal_web/ http://192.168.8.7:8080/paymentrecon-portal_web/
+ ProxyPassReverse /paymentrecon-portal_web/ http://192.168.8.7:8080/paymentrecon-portal_web/
+
+ ProxyPass /api/ http://192.168.8.6:8080/api/
+ ProxyPassReverse /api/ http://192.168.8.6:8080/api/
+
+ # Do not ProxyPass / to 8080 for the main SPA when using DocumentRoot
+
+```
+
+**Deploy:** After each frontend release, sync `build/` to
+`/var/www/pnsps/build` (or your chosen path) on the Apache server.
+
+**Pros:** Clear split — static on Apache, API on Spring. Easy to reason
+about 200 vs 404.
+
+**Cons:** Requires a deploy path for static files on the Apache tier.
+
+---
+
+## Option B: Keep `ProxyPass /` — rewrite deep links to `index.html` before proxy
+
+Use when the React app must stay on `192.168.8.6:8080` and you only
+control Apache. Rewrite eligible **GET** requests to `/index.html` on the
+upstream; browser URL and query string stay unchanged (`[PT,QSA]`).
+
+Add **inside the same vhost**, after enabling `RewriteEngine` (you may
+already have TRACE/TRACK rules):
+
+```apache
+RewriteEngine on
+
+# Existing: deny TRACE / TRACK
+RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
+RewriteRule .* - [F]
+
+# Do not SPA-rewrite API or other proxied applications
+RewriteCond %{REQUEST_URI} ^/api/ [OR]
+RewriteCond %{REQUEST_URI} ^/paymentrecon-dept_web/ [OR]
+RewriteCond %{REQUEST_URI} ^/paymentrecon-portal_web/
+RewriteRule ^ - [L]
+
+# Pass through static assets (Create React App uses /static/...)
+RewriteCond %{REQUEST_URI} ^/static/ [OR]
+RewriteCond %{REQUEST_URI} \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|map|json|txt|webmanifest)$ [NC]
+RewriteRule ^ - [L]
+
+# SPA fallback: upstream receives GET /index.html; browser keeps /paymentPage/callback?...
+RewriteCond %{REQUEST_METHOD} GET
+RewriteCond %{REQUEST_URI} !^/index\.html$
+RewriteRule ^ /index.html [PT,QSA,L]
+
+# Existing ProxyPass (specific paths first)
+ProxyPass /paymentrecon-dept_web/ http://192.168.8.7:18080/paymentrecon-dept_web/
+ProxyPassReverse /paymentrecon-dept_web/ http://192.168.8.7:18080/paymentrecon-dept_web/
+
+ProxyPass /paymentrecon-portal_web/ http://192.168.8.7:8080/paymentrecon-portal_web/
+ProxyPassReverse /paymentrecon-portal_web/ http://192.168.8.7:8080/paymentrecon-portal_web/
+
+ProxyPass /api/ http://192.168.8.6:8080/api/
+ProxyPassReverse /api/ http://192.168.8.6:8080/api/
+
+ProxyPass / http://192.168.8.6:8080/
+ProxyPassReverse / http://192.168.8.6:8080/
+```
+
+If rewrite and proxy order behaves differently on your Apache version,
+try explicit proxy on the last rule:
+
+```apache
+RewriteRule ^ /index.html [P,QSA,L]
+```
+
+(requires `mod_proxy` and correct module load order).
+
+**Pros:** No change to where the WAR/static tree lives on 8080.
+
+**Cons:** Rewrite + `ProxyPass` interaction must be tested per environment;
+static file detection is by URL pattern, not upstream filesystem.
+
+---
+
+## Option C: SPA fallback on Tomcat (origin `:8080`)
+
+If Apache cannot change, configure the tier that serves `/` on port 8080
+(Tomcat `RewriteValve`, filter, or error handling) so unknown **GET**
+paths return `index.html` with **200**. Same idea as Options A/B; only the
+layer moves.
+
+---
+
+## Verification checklist
+
+Replace host with DEV/UAT/PROD as appropriate (`pnspsdev.gld.gov.hk`,
+`pnsps.gld.gov.hk`, etc.).
+
+### 1. Payment callback document (main fix)
+
+```bash
+curl -sS -D - -o /dev/null \
+ "https://pnspsdev.gld.gov.hk/paymentPage/callback?TRANSACTION_ID=test&RECEIPT_TOKEN=test"
+```
+
+| Expected after fix | Before fix (typical) |
+|---|---|
+| **200**, `Content-Type: text/html` | **404**, HTML body may still be SPA |
+
+### 2. API still proxied (not rewritten to index)
+
+```bash
+curl -sS -D - -o /dev/null -X POST \
+ "https://pnspsdev.gld.gov.hk/api/payment/callback/status" \
+ -H "Content-Type: application/json" \
+ -d '{}'
+```
+
+Expect a **JSON** error from Spring (4xx), **not** `index.html` HTML.
+Status is not 404 from a missing static file.
+
+### 3. Static assets
+
+```bash
+curl -sS -D - -o /dev/null \
+ "https://pnspsdev.gld.gov.hk/static/js/main..chunk.js"
+```
+
+Use a real hash from the deployed `index.html`. Expect **200**.
+
+### 4. Other client routes
+
+```bash
+curl -sS -D - -o /dev/null "https://pnspsdev.gld.gov.hk/login"
+curl -sS -D - -o /dev/null "https://pnspsdev.gld.gov.hk/dashboard"
+```
+
+Expect **200** HTML shell when logged-out routes are publicly reachable
+(may redirect in browser; curl shows initial response).
+
+### 5. Browser — card payment end-to-end
+
+1. Start card payment; complete or cancel on GCIS gateway.
+2. DevTools → Network: document for `/paymentPage/callback?...` → **200**.
+3. Network: `POST .../api/payment/api/updatepayment` and
+ `POST .../api/payment/callback/status` → success responses per business rules.
+4. UI: success, cancel, or fail page from `PaymentCallback.js`.
+
+### 6. Apache reload
+
+```bash
+sudo apachectl configtest
+sudo systemctl reload apache2
+```
+
+Use `httpd` / `apachectl` as on your GLD hosts.
+
+---
+
+## Application behaviour (after shell loads)
+
+### Auth gate
+
+`paymentPage/callback` is under `PublicUserRoutes`, which is registered
+only when the user is logged in as IND or ORG (`src/routes/index.js`).
+If the session expires on the gateway, the user may hit `/login` instead
+of the callback page. That is separate from the document 404; test with a
+fresh login if callback APIs never fire.
+
+### FPS vs card
+
+| Flow | Return path | Route file |
+|---|---|---|
+| Card | `/paymentPage/callback` | `PublicUserRoutes.js` |
+| FPS (PASG) | `/paymentPage/fps/fpscallback` | `PublicUserRoutes.js` (+ test route in `LoginRoutes.js`) |
+
+This follow-up focuses on **card** `returnurl` to `/paymentPage/callback`.
+
+### Code fragility (optional app fix, not Apache)
+
+`PaymentCallback.js` uses
+`params.get("RECEIPT_TOKEN").replace("%3D", "=")` without a null check.
+Missing `RECEIPT_TOKEN` throws before API calls. Harden separately if
+gateways ever omit the param.
+
+---
+
+## What not to do
+
+| Idea | Why |
+|---|---|
+| Add only a React route | Does not fix the first GET to Apache/Tomcat |
+| `Redirect` callback URL to `/` | Loses query params unless carefully preserved |
+| Rewrite `/api/` to `index.html` | Breaks payment POSTs, logout, login, etc. |
+| Rely on 404 + `index.html` body | Payment may work; monitoring and CSP reports stay noisy |
+| Change CSP for this | Not a CSP block; document status is HTTP routing |
+
+---
+
+## Decision summary
+
+| Option | Where SPA fallback lives | Remove `ProxyPass /`? |
+|---|---|---|
+| A | Apache `DocumentRoot` + `FallbackResource` | Yes (for main site) |
+| B | Apache `RewriteRule` → `/index.html` + existing proxy | No |
+| C | Tomcat / origin on 8080 | No Apache change |
+
+**Recommended:** Option A if operations can publish `build/` to Apache;
+Option B if the static tree must remain on `192.168.8.6:8080`.
+
+After implementation, `/paymentPage/callback` should show **200** on the
+document request while behaviour of `PaymentCallback.js` and payment APIs
+stays the same.
+
+---
+
+## Code map (quick reference)
+
+| Piece | Location |
+|---|---|
+| Callback route | `PNSPS-frontend-MaterialUI/src/routes/PublicUserRoutes.js` |
+| Callback UI + API calls | `PNSPS-frontend-MaterialUI/src/pages/Payment/PaymentCallback.js` |
+| Card return URL | `PNSPS-frontend-MaterialUI/src/pages/Payment/Card/index.js` |
+| API constants | `PNSPS-frontend-MaterialUI/src/utils/ApiPathConst.js` |
+| Route auth wiring | `PNSPS-frontend-MaterialUI/src/routes/index.js` |
+| Payment APIs | `PNSPS-backend/.../payment/web/PaymentController.java` |
+| API context path | `PNSPS-backend/src/main/resources/application.yml` (`/api`) |
+
+---
+
+## Change log (doc)
+
+| Date | Note |
+|---|---|
+| 2026-10-14 | Initial follow-up from payment callback 404 investigation and DEV vhost review |