Automate expense reimbursement with n8n and a receipt API

MacBook Pro on top of brown table

Manual expense reimbursement is a tax on your team’s time. Someone photographs a receipt, emails it, someone else opens it, types numbers into a spreadsheet, then pings a manager. Every step is a handoff that can break.

This tutorial walks through an n8n workflow that eliminates most of those handoffs. It parses receipt images via API, routes expenses by amount, logs approvals to Google Sheets, and notifies submitters via Slack or Gmail. No code required.

️ What You’ll Build

The workflow does five things in sequence:

  1. Triggers when a receipt image is submitted via webhook, Gmail, or Google Drive
  2. Calls the Receipt Parser API and gets back structured JSON
  3. Routes based on amount: auto-approves expenses under $50, flags anything over for manager review
  4. Logs every expense to a Google Sheet
  5. Notifies the submitter via Slack or email with the parsed details

Prerequisites

  • An n8n instance, cloud or self-hosted (n8n.io)
  • A Receipt Parser API key from ilovesreceipt.com (free tier: 500 calls/month, no credit card required)
  • A Google account for Sheets logging
  • Optional: a Slack workspace for notifications
a person is using a pos machine in a store

Step 1: Set Up the Trigger

Pick your entry point based on how receipts arrive. Three options:

  • Webhook (recommended): Add a Webhook node, set method to POST. Any tool that can send a webhook, including forms, mobile apps, and Zapier, can trigger this workflow.
  • Gmail: Add a Gmail Trigger node. Filter by subject containing “receipt” or “reimbursement”. Fires on each matching email with an attachment.
  • Google Drive: Add a Google Drive Trigger node. Watch a specific folder such as /Receipts/Pending. Fires when any new file is uploaded.

The rest of this tutorial uses the webhook option since it’s the most reusable across intake methods.

Step 2: Read the File

No conversion step needed. The Receipt Parser API accepts the raw file as multipart/form-data, so base64 encoding is not required.

If your trigger provides a URL (for example, a Google Drive file URL), add an HTTP Request node set to GET to download the binary first. If your trigger delivers a binary attachment directly, such as a Gmail attachment, pipe it straight to Step 3.

Step 3: Call the Receipt Parser API

Add an HTTP Request node with these settings:

FieldValue
MethodPOST
URLhttps://web-production-58295.up.railway.app/api/parse
AuthenticationHeader Auth
Header nameAuthorization
Header valueBearer {{ $credentials.receiptParserKey }}
Body Content TypeForm Data (multipart)
Body field namefile
Body field valueBinary data from previous node

Store your API key in n8n Credentials as a Generic Credential with Authorization → Bearer YOUR_KEY. This keeps it out of the workflow JSON and reusable across other flows. Get a free key at ilovesreceipt.com.

After this node runs, structured data is available in subsequent nodes as $json.data.merchant.name, $json.data.total, and so on.

Step 4: Route by Amount (IF Node)

Add an IF node with this condition:

{{ $json.data.total }} > 50
  • True branch: flag for manager review
  • False branch: auto-approve and log

You can layer additional conditions if needed: category-based routing (meals vs. travel vs. supplies), a merchant allowlist or blocklist, or employee-specific thresholds.

A calculator sitting on top of a pile of money

Step 5: Log to Google Sheets

On the False (auto-approved) branch, add a Google Sheets node. Set Operation to Append Row, point it at your expense log spreadsheet, and map these columns:

ColumnValue
Date{{ $json.data.date }}
Merchant{{ $json.data.merchant.name }}
Total{{ $json.data.total }}
Tax{{ $json.data.tax }}
Tip{{ $json.data.tip }}
Payment{{ $json.data.payment_method }}
StatusAuto-Approved
Submitted{{ $now }}

Step 6: Flag Large Expenses in Slack

On the True (high expense) branch, add a Slack node. Set Operation to Send Message, target the #expense-approvals channel, and use this message template:

  *Expense Approval Required*

*Merchant:* {{ $json.data.merchant.name }}
*Amount:* ${{ $json.data.total }}
*Date:* {{ $json.data.date }}
*Payment:* {{ $json.data.payment_method }}

React ✅ to approve or ❌ to reject.

Step 7: Notify the Submitter

On both branches, add a Gmail or Slack node to confirm receipt. Use this message body:

Hi there — your expense was received and parsed successfully.

Merchant: {{ $json.data.merchant.name }}
Date: {{ $json.data.date }}
Total: ${{ $json.data.total }}

{{ $json.data.total > 50 ? "Your expense has been flagged for manager review." : "Your expense has been auto-approved and logged." }}

Testing the Workflow

Open the workflow in n8n and click Execute Workflow with test mode on. Then send a POST request to your webhook URL with a receipt image:

curl -X POST https://your-n8n-instance.com/webhook/receipt-parse 
  -F "[email protected]"

Check your Google Sheet for the logged row and your Slack channel for any approval notifications.

Going Further

Once the core workflow is running, a few extensions are worth considering:

  • Multi-currency support: The API detects currency. Add a conversion step using an exchange rate API to normalize totals.
  • PDF invoices: The API handles PDFs too, useful for contractor invoices submitted via email attachment.
  • Airtable instead of Sheets: Swap the Google Sheets node for an Airtable node for richer filtering and views.
  • Approval loop: Use n8n’s Wait node to pause the workflow until a Slack reaction is received before marking the expense as approved.

Common Pitfalls

  • Binary data not passing through: Make sure the node connecting to the HTTP Request node is set to pass binary data, not just JSON. Check the node’s output panel to confirm a binary item is present.
  • API key exposed in workflow JSON: Always use n8n Credentials for the API key. Pasting it directly into the node makes it visible to anyone who exports the workflow.
  • IF node evaluating strings instead of numbers: If $json.data.total comes back as a string, wrap it in Number() or use n8n’s expression editor to cast it before the comparison.
Stay on top of AI & Automation with BizStack Newsletter
BizStack  —  Entrepreneur’s Business Stack
Logo