GET /incoming/{path} returned HTTP 200 but was a dead end: the hook node’s snapshot remained empty (no request data stored), and downstream observers (Calculations wired with sources=[hook], SOURCE_INVOKED) never fired.
Two gaps in ServerWebHookInboundProcessor.process() and Routes.kt:
No payload written. Routes.kt built allParams (path segments + query params) and sent it as the HTTP response body, but never stamped it into the hook node’s snapshot before calling invoke(). The processor received a node with an empty snapshot and published that empty state.
Wrong persistence pattern. The processor called runProcessing { succeeded(node) } without first calling update(node, propagate=false) to persist the snapshot. The ServerButtonProcessor works because it calls succeeded(node) directly — the Button has no value to persist. A value-producing source like IncomingWebHook must persist first (update propagate=false) then fire (succeeded), so observers receive the durable value. The runProcessing wrapper was also unnecessary (Button doesn’t use it).
Routes.kt: Serialize allParams as a JSON object, build an updatedHook with snapshot = Snapshot(now, payload), and pass updatedHook to invoke() instead of the original node.WebHookInboundProcessor.kt: Replace runProcessing { succeeded(node) } with update(node, propagate=false); succeeded(node) — mirroring the MQTT SUB processor (ServerMqttProcessor.subscribe).update(node, propagate=false) → succeeded(node). Calling only succeeded on a stale node publishes empty/stale values to observers.ServerWebHookInboundProcessorTest verifies the call sequence (update propagate=false before succeeded) via verifySequence.