How to build custom web UI for my n8n workflows using HTML/Javascript

Hi everyone, first time posting here!

So, I’ve built dozens of n8n workflows for my business over the past couple of years.

The workflows run fine, but when a human needs to make a decision before n8n continues, whether it’s approving an expense, reviewing a document, or confirming a piece of content before publishing, things start to get ugly.

The workflow needs to pause, ask for input, wait for an answer, and then continue based on the human decision.

The options always came down to:

- Native n8n forms: max 9 field types, no JavaScript, can’t add branding assets

- Email/Slack: this works, but now my UI lives inside someone else’s platform and I don’t have much control over it

- Retool, Softr, Bubble. etc.: separate tools with perpetual subscriptions

I recently watched a video of a talk that a friend of mine gave at the n8n Amsterdam meetup, and that’s when I found one of the most underused nodes in n8n…

The Wait node.

Like most people, I primarily used it to add a fixed pause and then resume the workflow. But Sherif showed how you can configure it to resume via webhook, which makes it pause execution indefinitely until an external event wakes it up.

He also showed how n8n assigned a unique execution ID to every single run, which allows any frontend to use that ID to wake up exactly the right paused workflow when someone makes a decision.

Combine those two pieces, and you can build custom UI for any n8n workflow using plain HTML, CSS, and JS.

Here is how it works:

  1. The frontend sends a POST to your n8n webhook URL for the trigger node

  2. n8n processes the request and sends back the execution ID and the request data via a Respond to Webhook node before pausing at the Wait node. The frontend now has everything it needs.

  3. The UI displays the request (amount, submitter, description, whatever your workflow sends back) and shows Approve/Reject buttons, or input fields if you need them.

  4. When the user clicks a button, the frontend POSTs `{ approved: true }` or `{ approved: false }` to `n8n-URL/webhook-waiting/{executionId}`. You can find the exact URL in the Wait node’s output during the first execution run.

  5. The workflow resumes, reads the decision, and routes accordingly. Once n8n processes the result, it sends a confirmation back to the UI.

No databases to store states. No third-party tools. The execution ID is the only piece of state the frontend needs to hold.

The full frontend is an HTML file, a CSS file, and a single JS file. No npm and no frameworks. It runs entirely on your existing host. I run mine on a self-hosted instance behind Tailscale and it works perfectly.

A word of caution: n8n execution IDs are sequential. If you put this in front of external users without additional security layer, someone could guess IDs and access or resume workflows that aren’t theirs. it’s fine for internal use, but for anything client-facing, you need to add an indirection layer on top of it to obfuscate those IDs.

If you want to try it out, you can download the workflow and HTML/JS files for free here.

Happy to answer any questions in the comments.

1 Like

Thanks for sharing,

Regarding this caution, you can always implement your own authentication to prevent that, such JWT..

1 Like

Or just use Valuya Guard - we combined authorization and payments - we are basically stripe combined with OAuth. Secure and monetizable.

1 Like

You’re welcome. And you’re right. That’s one good way to do it. I’ve been implementing UUIDs as a level of indirection between Exeuction IDs and the interface.

Thanks for the tip. I will check it out.

Why not hop on a call next week to explore synergies - always curious.