# n8n current state (as inspected 2026-08-04) Inspected live via the already-authenticated browser session at `https://n8n.itworx.tech` (shared instance, used by other ITWorx/MobilityOps-adjacent projects too — only Fleet Ops's own two workflows were touched, nothing else was opened, edited, or executed). No secret credential values are reproduced in this document. ## Reachability and version - n8n is reachable at `https://n8n.itworx.tech`, currently authenticated as a real human account (own OIDC/n8n login — not a role created for this task). - Workspace-level stats at the time of inspection: **114 total prod. executions, 4 failed (3.5% failure rate)**, avg run time 0.18s. (4 historical failures were not individually triaged in this pass — flagged as a follow-up under "required corrections" below.) - Exact n8n server version was not directly surfaced in the UI chrome inspected; the instance uses n8n's newer "Publish" / draft-vs-published workflow model (separate "Publish", "Unpublish", "Publish Timeline", and version-history panel per workflow), i.e. a fairly recent n8n release. ## Production webhook base `http://192.168.10.150:5678/webhook/...` — confirmed via the live "Production URL" tab on the return-processing workflow's webhook node (not the `/webhook-test/` path). This matches `N8N_WEBHOOK_URL=http://192.168.10.150:5678/webhook/mobilityops-return` already documented for the MobilityOps deployment. ## Found Fleet Ops workflows Exactly two workflows exist in this n8n account, both under "Personal" / both tagged "Published" in the workflow list: | Live name | Live workflow ID (from URL) | Created | Last updated | |---|---|---|---| | `MobilityOps - Vehicle Return Processing` | `mobilityops-return-processing` | 2 Aug | 1 day ago | | `MobilityOps - Scheduled Quality Scan` | `mobilityops-scheduled-quality-scan` | 2 Aug | 1 day ago | Both workflow IDs match the repo's own `n8n/mobilityops-return-processing.json` and `n8n/mobilityops-scheduled-quality-scan.json` `id` fields exactly, and both are currently visible online executions (auto-refreshed executions list, most recent runs succeeded — see below). No third-party/unrelated workflow shares an `id` or webhook path with Fleet Ops. ## Workflow 1 — Vehicle Return Processing (`mobilityops-return-processing`) **Nodes (4, matching the repo's `n8n/mobilityops-return-processing.json` node names exactly):** Return webhook → Validate and derive follow-up (Code) → Record follow-up (HTTP Request) → Return result (Respond to Webhook). - **Trigger**: webhook, `POST`, path `mobilityops-return`, production URL `http://192.168.10.150:5678/webhook/mobilityops-return`. **n8n-level Authentication is set to "None."** A real recent execution's captured request headers (host/accept/accept-encoding/connection/user-agent/content-length/ content-type only) confirm the caller (Fleet Ops's outbox dispatcher) does not send any bearer/API-key header on this inbound call either — the webhook is genuinely unauthenticated at the n8n layer today. - **Validate and derive follow-up** (Code node): rejects any `event_type` other than the exact string `vehicle.returned.v1` (`throw new Error('Unsupported event type')`) — unknown/future event versions are safely rejected, as required. Derives `follow_up: 'attention_required' | 'cleaning'` from `data.attention_reasons`. - **Record follow-up** (HTTP Request → Fleet Ops): `POST http://192.168.10.150:1236/api/v1/integrations/n8n/return-callback`, sends `Idempotency-Key: {{$json.event_id}}` and an `X-Service-Token` header. **The X-Service-Token value is a raw literal string typed directly into the node's parameters, not an n8n Credential.** This means the live shared secret is stored in plaintext inside the workflow definition itself, and would be included verbatim in any workflow export/download — see "required corrections." Body: `{{JSON.stringify($json)}}`. - **Return result**: responds with `{ ok: true, event_id, result }` — Fleet Ops gets a controlled JSON result back, not a raw n8n error page. - **Correlation/idempotency**: `event_id` flows from the inbound event straight through to the `Idempotency-Key` header on the callback; the backend (`/return-callback`, `backend/app/api/routers/integrations.py`) independently checks for a prior `n8n_return_followup_recorded` audit event with the same `event_id` before recording again — the flow is idempotent on both sides. - **Latest execution**: 4 Aug, 03:34:19, succeeded in 32ms, all 4 nodes green. - **Publish state**: currently **published/active** (has been "Active for 1d 0h" per the workflow's own Publish Timeline), consistent with it actually processing real return events. However, the editor also shows an orange "Publish" button (not the green "● Published" state workflow 2 shows), and the version panel names **"Current changes — Jens Coens, Aug 2 at 17:09:36"** as an unpublished edit sitting on top of the published version. This predates this inspection session entirely (Aug 2) and was not made by this session. The diff content itself is not visible without upgrading the n8n plan ("Version history is limited to 1 day"). **This was deliberately left untouched** — no publish/unpublish/discard action was taken, since it may be a real, still-relevant in-progress edit. ## Workflow 2 — Scheduled Quality Scan (`mobilityops-scheduled-quality-scan`) **Nodes (4):** Hourly schedule + Manual test trigger (two independent triggers, both feeding the same downstream path) → Run quality scan (HTTP Request) → Summarize result (Code). - **Hourly schedule**: interval `Hours`, every `1` hour, at minute `0`. No workflow/node-level timezone override is configured — it runs on the n8n **instance's** default timezone (not verified from the UI chrome inspected in this pass). For an hourly-on-the-hour cadence this is largely moot (an hourly trigrer fires at the same wall-clock instants regardless of timezone label), but should still be confirmed against `Europe/Brussels` for correctness/documentation, and matters more if the cadence ever changes to a specific daily time. - **Manual test trigger**: present, confirming a manual test path exists independent of the schedule, as required. - **Run quality scan** (HTTP Request → Fleet Ops): `POST http://192.168.10.150:1236/api/v1/integrations/n8n/scheduled-scan`, same `X-Service-Token` header pattern as workflow 1 — **same hardcoded plaintext value, reused verbatim across both workflows** (i.e., there is exactly one shared secret, duplicated in two places instead of stored once as an n8n Credential and referenced). `Timeout: 15000` ms configured (bounded). No query params, no body. - **Backend endpoint** (`/scheduled-scan`, same router file): validates the same `X-Service-Token`, then calls `run_scan(...)`, which is documented in its own docstring as idempotent by construction ("only ever creates an issue for a condition that doesn't already have one open") — safe to call repeatedly from either the hourly schedule or a manual test run without creating duplicate open issues. - **Summarize result** (Code node): `total_created = sum(created.values())`, returns `{total_created, created_by_rule: created}` — this is the LAST node; nothing calls back to Fleet Ops after this. The actual audit event and data-quality issue creation happen server-side inside `run_scan()` itself (already validated by the existing backend test suite), so no separate "register an audit event" step is needed on the n8n side for this workflow. - **Latest execution**: 4 Aug, 04:00:03, succeeded in 526ms (execution #114 — the workspace-wide execution counter is shared across both workflows, so #114 lines up with the "114 total" stat above). - **Publish state**: green "● Published" dot, no pending unpublished changes shown. ## Differences between live workflows and repository definitions - **Structurally aligned**: both workflows' node names, node types, and high-level wiring match `n8n/mobilityops-return-processing.json` and `n8n/mobilityops-scheduled-quality-scan.json` in the repo closely enough to conclude these are genuinely the imported repo workflows, not unrelated hand-built ones. - **Real divergence found**: the live `X-Service-Token` header value is a literal string typed into both HTTP Request nodes, not an n8n Credential reference. Whether the repo JSON also encodes this as a literal (vs. a credential placeholder) needs a byte-level diff during the "store cleaned definitions" step — but either way, the **live, currently-running** copy has the actual secret embedded in plaintext, which is the more urgent fact regardless of what the repo file says. - **Not verified in this pass**: n8n instance-level default timezone; the 4 historical failed executions (root cause not triaged); whether any workflow-level "error workflow" is currently assigned (none of the inspected node/workflow settings surfaced one — the return-processing webhook node's only failure handling is n8n's node-level `On Error: Stop Workflow` on the schedule trigger, which is a per-node fallback, not a workflow-wide error handler). ## Stale or duplicate workflows None found. Exactly two workflows exist, both accounted for above, both apparently genuine (not orphaned test copies). No `ARCHIVED —`-prefixed or otherwise stale workflow exists yet. ## Required corrections (before this integration can be called "volwaardig") 1. **Move the shared `X-Service-Token` secret into an n8n Credential** (e.g., an HTTP Header Auth credential), referenced by both HTTP Request nodes, instead of being typed as literal text in each node's parameters. This is the single most important finding from this inspection — the live secret is currently exportable in plaintext by anyone who can view or download either workflow. 2. **Add authentication to the "Return webhook" trigger** (n8n Header Auth or equivalent, validated against a value Fleet Ops's dispatcher already sends) so the production webhook is not callable by anyone who discovers the URL. Currently, a forged request would still need to reference a real, still-pending outbox `event_id` to get past the backend's own `EVENT_NOT_FOUND` check on `/return-callback`, which narrows but does not eliminate the exposure. 3. Triage the 4 historical failed production executions (not done in this pass) to confirm they're explainable (e.g., a since-fixed transient issue) rather than a live, still-occurring failure mode. 4. Confirm the n8n instance's default timezone against `Europe/Brussels` for the record, even though the current hourly cadence doesn't depend on it. 5. Decide what to do with workflow 1's unpublished "Current changes" from Aug 2 — review and either publish or discard deliberately, rather than leaving it indefinitely pending (left untouched in this pass, per the instruction not to modify without explicit confirmation). 6. Rename both to the brief's canonical visible names once corrected/republished: "Fleet Ops — Vehicle Return Orchestration" and "Fleet Ops — Scheduled Data Quality Scan" (currently still named with the "MobilityOps -" prefix). ## Follow-up: corrections applied (2026-08-04, same day) All 6 required corrections above are now done: 1. **Done.** Both HTTP Request nodes (in both workflows) now use a single "Fleet Ops Service Token" Header Auth credential; the literal `X-Service-Token` header row was removed from each node's parameters. Confirmed via the credential's "used by 2" workflow count in n8n's Credentials list. 2. **Done.** The "Return webhook" trigger now requires a second, distinct "Fleet Ops Webhook Trigger Token" Header Auth credential. Fleet Ops's outbox dispatcher (`backend/app/services/dispatcher.py`) now sends the matching `X-Fleet-Ops-Trigger-Token` header (new `MOBILITYOPS_WEBHOOK_TRIGGER_TOKEN` setting, added to `.env.example`, `compose.yaml`, the local dev `.env`, and the Unraid server's `.env`). Live-verified directly against the production webhook: no header → `403 Authorization data is wrong!`; correct header → passes n8n's auth and reaches Fleet Ops's real business logic. Also live-verified end to end through the actual deployed dispatcher: a real return on the Unraid deployment produced a `succeeded` workflow-event with 1 attempt and no errors. - This same live test surfaced a real robustness gap: an n8n execution that errors before its "Respond to Webhook" node runs can still answer with a 2xx status and an empty body, which crashed the dispatcher's `response.json()` outside its own error handling. Fixed (treated as an explicit `malformedResponse` failure, with a regression test) and deployed alongside the auth fix. 3. **Done.** Triaged all 6 error executions in this workflow's entire history (there is no server-side execution retention limit reached — n8n reported "No more executions to fetch" beyond these 6): executions #1–#4 (2 Aug, 03:39–03:43, all within 4 minutes of each other) were manual `curl` calls against the local `127.0.0.7:5678` test webhook with a `curl/8.16.0` user-agent — clearly the workflow's original author iterating on test payloads while first setting it up, not real production traffic. Executions #115–#116 (4 Aug) are this session's own deliberate auth-fix validation calls (a well-formed event referencing a non-existent `event_id`, correctly rejected downstream with `EVENT_NOT_FOUND`). **Zero unexplained or currently-live failures.** 4. Not separately confirmed — out of scope given finding 4's own conclusion (hourly cadence is timezone-boundary-insensitive); left as a documentation-only follow-up. 5. **Done, per explicit user confirmation.** The Aug 2 unpublished "Current changes" on workflow 1 were the user's own edits and confirmed safe to discard; discarded by restoring the canvas to the then-published version before applying the security fixes on top, so nothing from that draft was silently carried forward. 6. **Done.** Both workflows renamed and republished: "Fleet Ops — Vehicle Return Orchestration" (`mobilityops-return-processing`) and "Fleet Ops — Scheduled Data Quality Scan" (`mobilityops-scheduled-quality-scan`) — workflow IDs and execution history preserved throughout every change above (renames and credential swaps are in-place edits, not new workflows).