How should I model a human image-review checkpoint without keeping an n8n execution open?

I am designing an n8n workflow for creative briefs where image generation and review are not fully automated. A brief enters through a form, is stored with a request ID, and is assigned to a designer. The designer may create visual drafts in an independent browser tool such as Nano Banana 2 Lite and then add the selected draft URL to the record. This is a third-party site, not a native n8n integration and not affiliated with Google or DeepMind.

The workflow then needs to notify a reviewer, collect either an approve or revise decision, and continue without leaving one n8n execution waiting for hours or days.

My proposed structure is:

  1. Workflow A receives the brief and creates a database row with waiting_for_draft.
  2. A webhook receives the draft URL and changes the status to waiting_for_review.
  3. n8n sends two signed links to the reviewer.
  4. Workflow B validates the signed response, writes the decision, and starts the publish or revision branch.

Would using separate event-driven workflows be the normal pattern here, or is the Wait node appropriate for a review window that may last several days? I prefer separate workflows for retries, but I need to prevent duplicate clicks from advancing the same request twice.

For idempotency, would you use a database unique constraint on request ID plus decision version, or rely on n8n execution data and static data? I would appreciate examples from other human-in-the-loop approval workflows, especially where external creative tools have no direct node.

For a review window that may last hours or days, separate event-driven workflows are the safer default. I would not use n8n execution data or static data as the source of truth.

Keep the request state in your database and treat approval as a compare-and-set operation:

  • The signed link carries request_id, decision_version, action, and an expiry.
  • Workflow B accepts it only while the record is still waiting_for_review and decision_version matches.
  • Update the status and decision atomically, backed by a unique constraint on request_id plus decision_version.
  • Only after that commit should you trigger the publish or revision workflow.
  • A duplicate click should return an already-processed result instead of starting the branch again.

The Wait node is reasonable for shorter, bounded waits. For multi-day human review, separate workflows are easier to retry, audit, and recover. Which database are you using? The exact idempotency pattern depends on its transaction or conditional-update support.

Hi @Martyn_Foster
Before idempotency, the thing that quietly breaks signed-link approvals is link pre-fetching. If the approve and revise links are GET URLs pointing straight at Workflow B’s Webhook, corporate mail scanners like Outlook Safe Links and Mimecast fetch both on delivery, so a decision gets written before the reviewer ever opens the message. Have each signed link hit a GET that only renders a confirmation page through Respond to Webhook, then commit the decision on the POST that the page’s button sends, so a real human click is the only thing that mutates state.

Building on Anshul’s prefetch warning and agentklaar’s conditional-update approach, I’d close one more failure gap: the approval can be committed successfully, but n8n can fail before it starts the publish/revise branch.

The clean way to avoid that is to make the state transition and the creation of the next piece of work atomic.

If the database is Postgres/Supabase, I would handle the review POST through a database function that:

  1. conditionally updates the request only when status = 'waiting_for_review' and decision_version still matches;

  2. records the decision;

  3. inserts an outbox job for the publish/revise branch;

all in the same transaction.

The outbox row can have a unique key on (request_id, decision_version). A separate n8n workflow processes pending outbox rows and marks them completed. That closes the failure gap where the approval is committed but n8n crashes before starting the next workflow, while retries remain safe.

If the conditional update affects zero rows, return already_processed or stale_decision rather than treating it as an error.

The Wait node can still be convenient, but I would keep the database, not the suspended execution, as the source of truth for a review that may last several days.

@agentklaar s pattern is right, and it maps almost exactly onto a trigger-node design pattern I’ve been using: don’t hold Workflow B open waiting for the reviewer at all. A dedicated trigger node registers its own webhook when the request goes out, and the actual second execution only starts when the signed approval response comes back — whether that’s ten minutes or four days later. Zero held-open executions, no Wait node babysitting a multi-day window.

Combine that with agentklaar’s compare-and-set + decision_version for the idempotency side, and hash-chain the decision log if this ever needs to prove after the fact that a reviewer’s decision wasn’t altered later — cheap to add now, expensive to retrofit once someone asks you to prove it. I built a general version of exactly this (trigger + atomic claim + idempotency + tamper-evident log) for cases needing a human checkpoint on any action, not just AI-agent ones — happy to share if useful, but agentklaar’s structure is the right foundation regardless of what you use to fill it in.