Marsen n8n approval webhook recovery lab v1.0.0 Recorded run: 5 October 2026. n8n 2.35.4, Node 24.18.1. Purpose Exercise authenticated callbacks, exact-payload approval, duplicate delivery, durable decisions and destination receipt reconciliation across process restarts. The destination and proposals are fictional. This is a reproducible teaching lab, not a deployed approval service, human review portal or production CRM template. Reproduce on Linux with Python 3 and Docker 1. Download and unzip recovery-lab.zip into a fresh directory. 2. Review workflow.json, fixture-server.mjs, test-lab.mjs and run-lab.py. 3. Run: sudo python3 run-lab.py Run without sudo only if your Linux UID is 1000 and you can use Docker. 4. Open test-results.json. Expect total=18 and passed=18. Inspect each case, the separate n8n execution records and preRestartEvidence. 5. Inspect cleanup.json for productionContainersStillRunning=true and labContainersRemoved=true. Only names observed before the run are checked. Docker downloads docker.n8n.io/n8nio/n8n:2.35.4 if it isn't cached. The runner creates a uniquely named internal Docker network and two limited containers. It publishes no host port and mounts no live n8n state. Credentials are random, temporary lab credentials generated for this run. The runner kills and restarts only those two named containers, exports evidence and removes the containers and network in its cleanup block. Temporary credential files and disposable n8n state are removed. Synthetic fixture state remains at the printed path; review it, then remove that exact temporary directory when no longer needed. Runtime logging is saved locally, not included in the published evidence bundle. The recorded run used ARM64 Linux; other host platforms were not evaluated. Workflow and reviewer identity The import file uses three built-in nodes: Authenticated callback -> Record exact decision -> Reconcile then deliver. The callback is POST /webhook/approval-recovery-lab in the disposable instance. Header Auth authenticates one fixed lab reviewer-a credential. The HTTP Request node sets reviewer-a itself; incoming actor/reviewer fields are ignored. This maps one credential to one reviewer, and does not implement multi-user identity. The two credential references in workflow.json contain names/IDs, never tokens. Importing the JSON alone is insufficient: it needs the private fixture service, seeded proposals and matching credentials. The runner configures these together. Do not activate this workflow on a live n8n instance. Durable state and destination fixture-server.mjs uses separate SQLite databases for approvals and destination receipts. BEGIN IMMEDIATE and a unique proposal decision prevent two concurrent callbacks from making conflicting decisions in this single-process fixture. An approval records a pending delivery before the destination is called. The writer checks the stable action ID for an existing receipt before creating a synthetic destination entry. A receipt and target version update are committed in one destination transaction. This atomic behavior belongs to the fixture; it does not demonstrate that a real CRM supplies the same capabilities. Decisions bind proposal ID, version, target and note. Expiry uses Date.now() on the fixture server and treats now >= expires_at as expired. A rejected decision is terminal. A replay must still pass reviewer, version and payload checks. 18 recorded scenarios Authenticated approval; missing credential; incorrect credential; wrong owner; spoofed body identity; changed note; changed target; old version; rejection followed by approval; expired server-clock deadline; invalid decision; ten concurrent approvals; concurrent approve/reject; target-version conflict; changed replay after a write; pending proposal across restart; failure before write followed by recovery; committed write with lost response followed by receipt reconciliation after restart. The initial two delivery faults are expected failed n8n executions. Passing the test means the ledger and final destination match the scenario's expectation, not that every execution has status=success. For approve/reject concurrency, whichever valid decision commits first is terminal; the test accepts either winner and verifies its matching destination effect. Fault and restart boundaries The lost-response case deliberately returns HTTP 504 after a committed fixture write. The before-write case returns HTTP 503 without a write. These are injected HTTP responses, not measured provider network timeouts. Both n8n and the fixture receive SIGKILL after the initial callbacks have completed, then start with the same isolated database mounts. The harness resubmits the same exact callback once, and verifies one receipt. Recovery is explicitly initiated by the test; no autonomous recovery worker, scheduler or retry policy is included. Not measured Real CRM integration, real reviewer sessions, Wait-node resumption, queue mode, multiple workers, multiple fixture processes, database-level crash during a transaction, heavy load, retention, backup recovery or customer outcomes. Use the separate production acceptance worksheet for checks in your real stack. Files workflow.json: credential-free import graph. fixture-server.mjs: isolated durable decision and synthetic destination service. test-lab.mjs: assertions and HTTP callback driver, run in the private container. run-lab.py: reproducible container setup, restart, evidence export and cleanup. build-workflow.mjs: rebuilds workflow.json; doesn't change an imported workflow. test-results.json: original dated evidence; reproduction overwrites the local copy. manifest.json: SHA-256 of published source and evidence files. LICENSE.txt: MIT license for this example kit. Primary documentation https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/ https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/ https://docs.n8n.io/deploy/host-n8n/configure-n8n/use-the-command-line Guide: https://www.marsentech.com/guides/n8n-approval-webhook-recovery Earlier in-memory lab: https://www.marsentech.com/guides/n8n-human-approval-workflow