# Get a manager's approval in n8n

Send an expense, a discount or a purchase to the person who approves it, with the documents attached, pause the workflow until they decide, and route approve, changes, decline and no answer.

The same workflow approves anything one person must sign off: a discount for a deal, a purchase order, a holiday, a contract change. Swap the expense for your own data; the steps stay the same. Checked on self-hosted n8n 2.44 with version 0.13.0 of the node.

![The finished execution: When clicking Execute workflow, Expense claim, Download the receipt, Ask the manager, Wait for the decision and Route by decision, all green, with one item leaving the approve output to Approved: pay it out. The changes, decline and no answer outputs lead to their own nodes.](https://docs.formstep.io/guides/n8n/n8n-approval-workflow.png)

Import the whole workflow from

[formstep-expense-approval.json](https://github.com/formstep/n8n-nodes-formstep/blob/main/examples/formstep-expense-approval.json)

(**Workflows** › **Import from File**), then pick your credential and your form in **Ask the manager**.

## Before you start

- The Formstep node on an n8n with a public HTTPS address, and a [Formstep credential](https://docs.formstep.io/guides/n8n/connect/). See [Install the Formstep node in n8n](https://docs.formstep.io/guides/n8n/install-the-node/).

- Pro or Business to email the request to the manager. On Free, leave **Delivery** at None and send the request’s link yourself.

## 1. Build the approval form

In Formstep, open **Templates**, pick **Approval** and click **Use template**. The template has a [Decision question](https://docs.formstep.io/requests/decisions-and-approvals/) with Approve, Decline and Request changes, a text box that shows when the manager asks for changes, and a signature that shows when they approve. Then make it about the expense:

- Add [hidden fields](https://docs.formstep.io/building-forms/hidden-fields/) for what the manager must see but never edit: `reference` (the template has it), `employee_name`, `amount` and `description`.

- Write them into the text with **@**, for example _@employee\_name asks you to approve an expense of @amount for @description, reference @reference._

- Add a [Documents block](https://docs.formstep.io/building-forms/documents-block/) named Receipt. The workflow attaches the receipt there for this manager only.

Publish the form. This is what the manager opens:

![The request page: Approve an expense, Grace Hansen asks you to approve an expense of NOK 3 700 for Hotel in Bergen, client workshop, reference EXP-1042, the attached Receipt EXP-1042 PDF, Your Name filled in with Maria Berg, Company locked to Acme Inc, and the Approve, Decline and Request changes options](https://docs.formstep.io/guides/n8n/n8n-approval-recipient.png)

## 2. Bring in the expense and its receipt

The example starts with an **Edit Fields** node named _Expense claim_ that holds the claim: `expenseId`, `employeeName`, `employeeEmail`, `managerName`, `managerEmail`, `amount`, `description` and `receiptUrl`. In your workflow, the claim comes from where it starts: a webhook from your expense tool, a new row in a sheet, a Formstep public link form the employee fills in.

An **HTTP Request** node named _Download the receipt_ fetches `{{ $json.receiptUrl }}` with **Response Format** set to **File**. The file lands in the binary field `data`. If the receipt arrives as an email attachment or a Google Drive file, use that node instead; anything that puts the file in a binary field works.

## 3. Ask the manager

Add a Formstep node, pick **Create request** and choose the approval form. Read the claim from the _Expense claim_ node, as `{{ $('Expense claim').item.json.managerEmail }}`: after the Wait node, `$json` is Formstep’s event, and the same expression still finds the claim.

![The Ask the manager node: Form Expense approval, Recipient Email from the Expense claim node, Values to Send with reference, amount, description and employee\_name marked context, Your Name and Company Acme Inc, Company locked under Read-Only Field Names or IDs, a document from the binary field data named Receipt with the expense ID in the Automatic Documents block, and Wait for the Outcome on](https://docs.formstep.io/guides/n8n/n8n-approval-request-node.png)

- **Values to Send** lists the hidden fields marked _· context_: the manager sees them in the text and cannot change them. **Your Name** and **Company** are prefilled, and **Read-Only Field Names or IDs** locks the company. See [Prefill, locked fields, and context](https://docs.formstep.io/requests/creating-requests/#three-buckets).

- Under **Documents**, the document takes the binary field `data`. **Automatic** puts it in the form’s only Documents block; pick a block when the form has several.

- Turn on **Wait for the Outcome**. Formstep then calls this execution back when the manager answers.

Under **Additional Fields**:

- **Delivery** _Email the Invitation_ emails the manager the link. The request’s reminders follow the form’s schedule.

- **Expires At** `{{ $now.plus({ days: 3 }).toISO() }}` ends the wait after three days.

- **External ID** `{{ $('Expense claim').item.json.expenseId }}-{{ $execution.id }}` ties the request to the claim. The execution ID keeps it unique: every execution has its own resume URL.

- **Test Mode**, on in the example, sends nothing and keeps the request out of your lists. Turn it off when the workflow goes live.

## 4. Wait and route the decision

Add a **Wait** node with **Resume** set to **On Webhook Call**. Then add a **Switch** node with three routing rules, each comparing `{{ $json.body.data.request.outcome }}` with `approve`, `changes` or `decline`, and each with **Rename Output** on. Under **Options**, set **Fallback Output** to **Extra Output** and name it _no answer_: an expired or canceled request has no outcome and goes there.

Each branch reads what it needs from the event and the claim:

```text
Who decided:        {{ $json.body.data.answers.full_name }}
What should change: {{ $json.body.data.answers.requested_changes }}
Why it ended:       {{ $json.body.type }}   (request.completed, request.expired or request.canceled)
The claim:          {{ $('Expense claim').item.json.expenseId }}
```

The example ends each branch in an **Edit Fields** node that writes the message. Put the node that sends it in its place: Gmail or Slack to the employee, your accounting tool for the payout, a second request to finance for _no answer_.

## 5. Run it

Click **Execute workflow**. The execution stops at the Wait node. Open the request’s `url` from the output of _Ask the manager_, decide, and submit: the execution resumes down the matching branch, as in the picture at the top. With Test Mode on, the callback still resumes the workflow; only the email stays unsent.

> **Two people, two steps**
>
> Chain two of these for a two-step approval: when the manager approves, a second Create request node sends the same claim to finance, with the manager’s name as context, and waits again. Each step keeps its own decision.

## Next

[Act on a request's outcome in n8n](https://docs.formstep.io/guides/n8n/request-outcome/) — Use a Formstep Trigger instead of waiting, for requests sent from anywhere.

[Read a PDF with AI and have the sender confirm it](https://docs.formstep.io/guides/n8n/read-a-pdf-with-ai/) — Let AI fill the form, and the person who knows check it.
