The answer. Build the Cloudflare Workflow as a drop-in executor behind the contract n8n already exposes — the same webhook path, the same execution status/stop API shape, the same signed callbacks — so the producer never knows which engine it is talking to. Failover is then a change to exactly three producer bindings: the webhook URL, the control-API base and the control-API key. Nothing else moves — not the accepted workflow id/version your publish gate checks. The one invariant you enforce by procedure is that a given run_id reaches one executor only; the engines may coexist for different runs, never the same one.
The pattern.
# api Worker [env.prod] — the only three values failover changes
EXECUTOR_WEBHOOK_URL = "https://<executor-host>/webhook/run" # n8n today
EXECUTOR_API_URL = "https://<executor-host>" # status / stop API
# EXECUTOR_API_KEY (secret): the backup accepts the same value
// backup executor Worker — n8n-compatible surface over a Workflow
if (req.method === "POST" && url.pathname === "/webhook/run") {
const raw = await req.text(); // raw bytes for the HMAC
if (!(await verifyHmac(env.HMAC_SECRET, raw, req.headers))) return new Response(null, { status: 401 });
if (env.EXECUTION_ENABLED !== "true") return new Response(null, { status: 503 }); // fail closed
const { run_id } = JSON.parse(raw);
const id = `run-${run_id}`; // deterministic: duplicate dispatch = same instance
await env.MY_WORKFLOW.create({ id, params: { run_id } }).catch(ignoreAlreadyExists);
return Response.json({ executionId: id }, { status: 202 });
}
Failover order: deploy the backup disabled → /health reports ready: true → record in-flight n8n executions → pause dispatch → flip the three values → resume with one keyword → let n8n drain. Failback is the mirror, ending with the backup disabled again.
The receipt. HarperFlow, 2026-07-25: the owner authorised exactly that three-binding exception and one producer dispatch. Production was restored to its exact pre-cutover version 21 seconds after cutover. The Workflow accepted the run and live-verified callbacks, discovery and retry behaviour; it stopped before article generation on a rejected third-party credential, so no article, CMS item, image or credit was consumed. The rule that fell out: don't restart a Workflow against a terminal failed run — production ignores status callbacks for terminal runs, so you'd create an artifact with inconsistent run state.
Watch out.
- Don't change the accepted workflow id/version because the executor changed; the backup emits the accepted producer identity so the publish gate stays put.
- Never delete Workflow instances during an incident — their retained state is the evidence for classifying an ambiguous external write.
EXECUTION_ENABLED=falseby default in every lane; a validly signed dispatch to a disabled backup still fails closed.
Related: production-canary-version-pinned-cutover · n8n-code-nodes-compiled-with-parameter-hash-contract · workflows-step-do-is-an-rpc-receiver