DEV Community

vernonroque
vernonroque

Posted on

How to Automate Expense Reimbursement with n8n and Receipt Parser API

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:

  1. Triggers when a receipt image is submitted (email attachment, Google Drive upload, or webhook)
  2. Parses the receipt using the Receipt Parser API โ†’ returns structured JSON
  3. Routes based on amount: auto-approves small expenses, flags large ones for manager review
  4. Logs every expense to a Google Sheet
  5. 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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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.
Enter fullscreen mode Exit fullscreen mode

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." }}
Enter fullscreen mode Exit fullscreen mode

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:

Download receipt-expense-reimbursement.json

After importing, you'll need to:

  1. Create a Header Auth credential named Receipt Parser API with Authorization: Bearer YOUR_KEY โ€” get your free key at ilovesreceipt.com
  2. Connect your Slack Account credential and update the channel names
  3. Connect your Google Sheets Account credential and replace YOUR_SPREADSHEET_ID with 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": "" }
}
Enter fullscreen mode Exit fullscreen mode

Testing the Workflow

  1. Open the workflow in n8n
  2. Click Execute Workflow with test mode on
  3. 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"
Enter fullscreen mode Exit fullscreen mode
  1. 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)