Target keywords: n8n receipt automation, automate expense reimbursement workflow, n8n HTTP node API
Platform: dev.to (primary) + n8n Community Discord #showcase + Hashnode (cross-post)
Word count target: 1,200โ1,500 words
Meta title (60 chars): Automate Expense Reimbursement with n8n + Receipt API
Meta description (155 chars): Build an n8n workflow that parses receipt images, extracts structured data, and routes them for approval โ no code required. Step-by-step tutorial.
The API powering this workflow โ try it before you build:
๐ ilovesreceipt.com
Upload any receipt and see the structured JSON output in seconds.
What We're Building
An n8n workflow that:
- Triggers when a receipt image is submitted (email attachment, Google Drive upload, or webhook)
- Parses the receipt using the Receipt Parser API โ returns structured JSON
- Routes based on amount: auto-approves small expenses, flags large ones for manager review
- Logs every expense to a Google Sheet
- Notifies the submitter via Slack or email with the parsed details
No code required. Pure n8n nodes.
Prerequisites
- n8n instance (cloud or self-hosted โ n8n.io)
- Receipt Parser API key from ilovesreceipt.com (free tier: 500 calls/month)
- Google account (for Sheets logging)
- Optional: Slack workspace for notifications
Workflow Overview
[Trigger] โ [HTTP Request: Parse Receipt] โ [IF: Amount > $50?]
โโโ YES โ [Slack: Flag for Review]
โโโ NO โ [Google Sheets: Log Expense]
โ
[Gmail/Slack: Notify Submitter]
Step 1: Set Up the Trigger
Choose your entry point based on how employees submit receipts:
Option A โ Webhook (most flexible):
Add a Webhook node. Set method to POST. This lets you trigger the workflow from any tool (form, mobile app, Zapier) that can send a webhook.
Option B โ Gmail (receipts by email):
Add a Gmail Trigger node. Filter by subject containing "receipt" or "reimbursement". The workflow fires each time a matching email arrives with an attachment.
Option C โ Google Drive:
Add a Google Drive Trigger node. Watch a specific folder (e.g., /Receipts/Pending). Fires when any new file is uploaded.
For this tutorial we'll use the Webhook option since it's the most reusable.
Step 2: Read the File
No conversion needed. The Receipt Parser API accepts the raw file directly as multipart/form-data โ no base64 encoding required.
If your trigger provides a URL (e.g. a Google Drive file URL), add an HTTP Request node set to GET to download the binary first. If your trigger provides a binary attachment directly (e.g. Gmail attachment), pipe it straight into Step 3.
Step 3: Call the Receipt Parser API
Add an HTTP Request node with these settings:
| Field | Value |
|---|---|
| Method | POST |
| URL | https://web-production-58295.up.railway.app/api/parse |
| Authentication | Header Auth |
| Header name | Authorization |
| Header value | Bearer {{ $credentials.receiptParserKey }} |
| Body Content Type | Form Data (multipart) |
| Body field name | file |
| Body field value | (binary data from previous node) |
Tip: Store your API key in n8n Credentials as a Generic Credential with Authorization โ Bearer YOUR_KEY. This keeps it secure and reusable across workflows. Get your free key at ilovesreceipt.com โ 500 calls/month, no credit card required.
After this node runs, you'll have the full parsed JSON available in subsequent nodes as $json.data.merchant.name, $json.data.total, etc.
Step 4: Add Routing Logic (IF Node)
Add an IF node to route based on the expense amount:
Condition:
{{ $json.data.total }} > 50
- True branch โ flag for manager review (high expense)
- False branch โ auto-approve and log
You can layer additional conditions:
- Category-based routing (meals vs. travel vs. supplies)
- Merchant allowlist/blocklist
- Employee-specific thresholds
Step 5: Log to Google Sheets
On the False (auto-approved) branch, add a Google Sheets node:
- Operation:
Append Row - Spreadsheet: your expense log sheet
- Sheet:
Expenses
Map these columns:
| Column | Value |
|---|---|
| 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 }} |
| Status | Auto-Approved |
| Submitted | {{ $now }} |
Step 6: Flag for Manager Review (Slack)
On the True (high expense) branch, add a Slack node:
- Operation: Send Message
- Channel:
#expense-approvals - Message:
๐งพ *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:
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." }}
The Complete Workflow (JSON Import)
You can import this workflow directly into n8n. Copy the JSON below and use File โ Import from JSON in n8n:
After importing, you'll need to:
- Create a Header Auth credential named
Receipt Parser APIwithAuthorization: Bearer YOUR_KEYโ get your free key at ilovesreceipt.com - Connect your Slack Account credential and update the channel names
- Connect your Google Sheets Account credential and replace
YOUR_SPREADSHEET_IDwith your actual sheet ID
{
"name": "Receipt Expense Reimbursement",
"nodes": [
{
"id": "1a2b3c4d-0000-0000-0000-000000000000",
"name": "Setup Instructions",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [240, 80],
"parameters": {
"width": 760,
"height": 180,
"content": "## โ๏ธ Setup Required Before Running\n1. **Receipt Parser API** โ Create a *Header Auth* credential named `Receipt Parser API`. Set header name to `Authorization`, value to `Bearer YOUR_API_KEY`. Get a free key (500 calls/month) at **ilovesreceipt.com**\n2. **Slack Account** โ Connect your Slack workspace in Credentials. Update `#expense-approvals` and `#YOUR_CHANNEL` to real channel names.\n3. **Google Sheets** โ Connect your Google account in Credentials. Replace `YOUR_SPREADSHEET_ID` with your actual spreadsheet ID (found in the sheet URL)."
}
},
{
"id": "1a2b3c4d-0001-0000-0000-000000000001",
"name": "Receipt Submitted",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [240, 320],
"webhookId": "receipt-parse-webhook-001",
"parameters": {
"httpMethod": "POST",
"path": "receipt-parse",
"responseMode": "onReceived",
"options": {}
}
},
{
"id": "1a2b3c4d-0002-0000-0000-000000000002",
"name": "Parse Receipt via API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [460, 320],
"parameters": {
"method": "POST",
"url": "https://web-production-58295.up.railway.app/api/parse",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"parameterType": "formBinaryData",
"name": "file",
"inputDataFieldName": "data"
}
]
},
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"id": "receipt-parser-api-cred",
"name": "Receipt Parser API"
}
}
},
{
"id": "1a2b3c4d-0003-0000-0000-000000000003",
"name": "Amount > $50?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [680, 320],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "amount-check",
"leftValue": "={{ $json.data.total }}",
"rightValue": 50,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
}
}
},
{
"id": "1a2b3c4d-0004-0000-0000-000000000004",
"name": "Flag for Manager Review",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [900, 180],
"parameters": {
"operation": "postMessage",
"channel": "#expense-approvals",
"text": "=๐งพ *Expense Approval Required*\n\n*Merchant:* {{ $json.data.merchant.name }}\n*Amount:* ${{ $json.data.total }}\n*Date:* {{ $json.data.date }}\n*Payment:* {{ $json.data.payment_method }}\n\nReact โ
to approve or โ to reject.",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"id": "slack-account-cred",
"name": "Slack Account"
}
}
},
{
"id": "1a2b3c4d-0005-0000-0000-000000000005",
"name": "Log to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [900, 460],
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Expenses",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"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 }}",
"Status": "Auto-Approved",
"Submitted": "={{ $now }}"
}
},
"options": {}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "google-sheets-account-cred",
"name": "Google Sheets Account"
}
}
},
{
"id": "1a2b3c4d-0006-0000-0000-000000000006",
"name": "Notify Submitter",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [1120, 320],
"parameters": {
"operation": "postMessage",
"channel": "#YOUR_CHANNEL",
"text": "=Hi there โ your expense was received and parsed successfully.\n\nMerchant: {{ $json.data.merchant.name }}\nDate: {{ $json.data.date }}\nTotal: ${{ $json.data.total }}\n\n{{ $json.data.total > 50 ? \"Your expense has been flagged for manager review.\" : \"Your expense has been auto-approved and logged.\" }}",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"id": "slack-account-cred",
"name": "Slack Account"
}
}
}
],
"connections": {
"Receipt Submitted": {
"main": [
[{ "node": "Parse Receipt via API", "type": "main", "index": 0 }]
]
},
"Parse Receipt via API": {
"main": [
[{ "node": "Amount > $50?", "type": "main", "index": 0 }]
]
},
"Amount > $50?": {
"main": [
[{ "node": "Flag for Manager Review", "type": "main", "index": 0 }],
[{ "node": "Log to Google Sheets", "type": "main", "index": 0 }]
]
},
"Flag for Manager Review": {
"main": [
[{ "node": "Notify Submitter", "type": "main", "index": 0 }]
]
},
"Log to Google Sheets": {
"main": [
[{ "node": "Notify Submitter", "type": "main", "index": 0 }]
]
}
},
"active": false,
"settings": { "executionOrder": "v1" },
"meta": { "instanceId": "" }
}
Testing the Workflow
- Open the workflow in n8n
- Click Execute Workflow with test mode on
- Send a POST request to your webhook URL with a receipt image:
curl -X POST https://your-n8n-instance.com/webhook/receipt-parse \
-F "data=@receipt.jpg"
- Check your Google Sheet for the logged row and Slack for any approval notifications.
Going Further
- Multi-currency support: The API detects currency โ add a conversion step using an exchange rate API
- PDF invoices: The API handles PDFs too โ great for contractor invoices submitted via email attachment
- Airtable instead of Sheets: Swap the Google Sheets node for an Airtable node for richer filtering
- Approval loop: Use n8n's Wait node to pause the workflow until a Slack reaction is received
Try the API First
Before building the workflow, see what the parsed JSON looks like for your receipt types:
๐ Live Demo โ no signup required
Ready to start building? Get your free API key at ilovesreceipt.com โ 500 calls/month, no credit card required.
Built this workflow or have a question about a specific node? Share it in the comments โ I'll help debug.
Top comments (0)