An approval workflow is the most reusable pattern in business automation. Build it once for expense reports and you have the template for purchase requisitions, time-off requests, vendor onboarding approvals, and any other process that requires a human decision before something moves forward.
This tutorial builds a complete approval workflow in n8n: a submission form, amount-based routing to the right approver, a notification with one-click approve or reject, and an automatic follow-up if no response comes within the defined window.
A reusable n8n approval workflow that:
The tutorial uses expense report approval as the example, but every node maps directly to purchase requisitions or any other approval use case.
n8n instance with version 1.0 or later. This workflow uses n8n's built-in Form Trigger and Wait node, both available in standard n8n.
Email or Slack for notifications. The tutorial uses email (Gmail or SMTP). If you prefer Slack, the notification node swaps in with the same logic.
A destination system for approved records. This could be Google Sheets (simple), Airtable, or an ERP via API. The tutorial shows Google Sheets as the write target, with notes on adapting to an ERP API at the end.
Defined approval rules. Before building, document: who approves what amounts, what the escalation path is if they do not respond, and what happens on rejection. The workflow cannot route what has not been defined.
n8n Form Trigger (submission)
→ Switch node (route by amount)
→ Branch A: Auto-approve (under $100)
→ Branch B: Manager approval ($100-$500)
→ Branch C: Director approval (over $500)
→ Send Approval Email (with approve/reject links)
→ Wait node (hold for response, max 24 hours)
→ IF node (approved or rejected)
→ Approved: Write to Google Sheets, notify requester
→ Rejected: Notify requester with reason
→ (Parallel) Schedule: Escalate if no response in 24 hours
In n8n, add an n8n Form Trigger node as the starting point. This creates a hosted form your team can submit from any browser.
Configure the form fields:
Click the form preview link to confirm the form renders correctly. Copy the production form URL: this is what you will share with your team.
For more complex deployments, you can replace the Form Trigger with a Webhook node and build the form in Retool or Tally. The rest of the workflow is identical.
Add a Switch node after the form trigger. This routes the submission to the correct approval branch based on the amount.
Configure three outputs:
In the Switch node, use the expression {{ $json.Amount }} to reference the submitted amount. Adjust the thresholds to match your actual approval matrix.
Connect Output 1 (under $100) directly to the approval action without a notification step. Add a Google Sheets node to log the approved record, then add a Gmail node to send the requester a confirmation.
Auto-approvals should still be logged. If an expense is automatically approved and later questioned, you need the record.
For the Manager and Director branches, add a Gmail node configured to send an approval request.
The email should contain:
To generate these links, you will use n8n's Wait node in the next step. Build a placeholder email first, then update the links once the Wait node is configured.
A clean subject line format: Approval Required: [Category] expense for $[Amount], [Requester Name]
The Wait node pauses the workflow until one of two things happens: the approver clicks a link, or the timeout expires.
Add a Wait node after the Gmail send. Configure it:
The Wait node generates two webhook URLs automatically: one for resuming when called. You will create two separate webhook paths for approve and reject by using the Wait node's resume URL with a query parameter to indicate the decision.
A simpler approach for this tutorial: create two separate workflows: one that handles the "approved" callback and one that handles the "rejected" callback - and link to each from the email. Each callback workflow writes to the appropriate destination and notifies the requester. This avoids the complexity of query parameter parsing while keeping the logic clean.
In the approved callback workflow (triggered when the approver clicks Approve):
In the rejected callback workflow (triggered when the approver clicks Reject):
For the escalation case (approver does not respond within 24 hours), add a separate workflow triggered by a schedule. This workflow:
This keeps the main approval workflow clean while ensuring nothing sits indefinitely.
Approvers clicking links multiple times. If the approver clicks Approve twice, the Google Sheets write may duplicate. Add a check in the approved workflow to see if the record has already been processed before writing.
Thresholds changing over time. The Switch node thresholds are hardcoded. Document them in the workflow notes and set a reminder to review them quarterly. If your approval matrix changes frequently, consider reading the thresholds from a Google Sheet so they can be updated without editing the workflow.
No rejection reason captured. The basic tutorial sends the approver directly to a URL. If you want to capture a rejection reason, replace the direct link with a short form (a second n8n Form Trigger) that asks for the reason before triggering the rejection path.
The same workflow applies to purchase requisitions with two changes:
Every other node is identical. The approval routing, wait logic, escalation, and notification structure transfers directly.
The Flow Kaizen guide covers how to document your approval matrix before building, the most common reason approval workflows require multiple rounds of revision is that the rules were not defined before the build started.