A webhook-triggered n8n workflow that automatically processes expense receipts from Airtable: downloads attachments, converts images to PDF, uploads to Dropbox, and updates record status - all with comprehensive error handling and notifications.
When a new expense is added to Airtable (or an existing expense is marked for processing), Airtable sends a webhook to n8n. This workflow automatically:
- Retrieves the expense record and attachment from Airtable
- Downloads the receipt file
- Converts images (JPG/PNG) to PDF using CloudConvert
- Uploads the PDF to Dropbox with a structured filename
- Updates the Airtable record to mark it as processed
- Sends email notifications if any step fails
Perfect for businesses tracking expenses, consultants managing client receipts, or teams needing organized receipt archiving.
- Webhook-Triggered: Instant processing when expenses are added
- Smart File Handling: Converts images to PDF, passes through existing PDFs unchanged
- Organized Storage: Files saved with consistent naming:
ExpenseName YYYYMMDD.pdf - Comprehensive Error Handling: Email notifications at every failure point
- Idempotent: Safe to retry - won't duplicate files or break on re-runs
- Status Tracking: Updates Airtable fields to track processing state
- Automated expense receipt archiving for accounting/tax purposes
- Client reimbursement documentation management
- Receipt collection for credit card reconciliation
- Automated backup of financial documents to cloud storage
Airtable Automation triggers webhook
→ n8n receives webhook with record ID
→ Fetch Airtable record
→ Download receipt attachment
→ Check file type
├─ Image (jpg/png): Convert to PDF via CloudConvert
└─ Already PDF: Skip conversion
→ Upload to Dropbox with structured filename
→ Update Airtable record status
→ Send success/failure notifications
This workflow demonstrates several production patterns:
- Graceful degradation: Each step validates success before proceeding
- Error isolation: Failures at any step trigger specific notifications
- Atomic operations: Dropbox upload and Airtable update are independent
- Clear audit trail: Email notifications provide complete failure context
Your Airtable Expenses table needs these fields:
| Field Name | Type | Description |
|---|---|---|
| Expense Name | Single line text | Description of the expense |
| Date | Date | When expense occurred |
| Amount | Currency | Expense amount |
| Category | Single select | Expense category |
| Receipt | Attachment | The receipt file (image or PDF) |
| Submitted | Checkbox | Marks expense as ready for processing |
| Synced to Dropbox | Checkbox | Workflow sets this when complete |
Create an Airtable automation:
Trigger: When record matches conditions
- Conditions:
Submitted = checkedANDSynced to Dropbox = unchecked
Action: Send webhook
- URL:
https://your-n8n-instance.com/webhook/airtable-receipt - Method: POST
- Body:
{
"recordId": "AIRTABLE_RECORD_ID()"
}Replace your-n8n-instance.com with your actual n8n webhook URL (found in the Webhook node after importing).
- n8n instance (cloud or self-hosted)
- Airtable Personal Access Token
- Dropbox OAuth2 credentials
- CloudConvert API account (free tier available)
- Gmail OAuth2 for notifications (or another email service)
- Download
workflow.jsonfrom this repository - In n8n: Import from File
- Select the downloaded JSON
Replace placeholder credential IDs:
Airtable Personal Access Token (YOUR_AIRTABLE_CREDENTIAL_ID)
- Scopes needed:
data.records:read,data.records:write,schema.bases:read - Used in: Get Airtable record, Update Airtable record
Dropbox OAuth2 (YOUR_DROPBOX_CREDENTIAL_ID)
- Scopes needed:
files.content.write - Used in: Add file to Dropbox
CloudConvert OAuth2 (YOUR_CLOUDCONVERT_CREDENTIAL_ID)
- Used in: CloudConvert node
- Free tier: 25 conversions/day
Gmail OAuth2 (YOUR_GMAIL_CREDENTIAL_ID)
- Used in: All notification nodes
- Alternative: Replace with email service of choice (SendGrid, Mailgun, etc.)
Replace these placeholders:
YOUR_AIRTABLE_BASE_ID- Your base ID (starts withapp...)YOUR_AIRTABLE_TABLE_ID- Your table ID (starts withtbl...)
Finding IDs: Airtable Help → API Documentation → locate IDs in the docs
In the "Add file to Dropbox" node, update the path:
path: "=/Receipts/{{ $('Get Airtable record').item.json['Expense Name'] }} {{ DateTime.fromISO($('Get Airtable record').item.json.Date).toFormat('yyyyMMdd') }}.pdf"Change /Receipts/ to your desired Dropbox folder path.
Replace YOUR_EMAIL@example.com in all Gmail notification nodes with your actual email address.
Notification types:
- Invalid webhook data
- Download failure
- Conversion failure
- Upload failure
- Airtable update failure
- Open the Webhook node
- Copy the Production URL
- Use this URL in your Airtable automation
- Add a test expense in Airtable with a receipt attachment
- Check the
Submittedcheckbox - Monitor n8n execution log
- Verify:
- File appears in Dropbox
Synced to Dropboxis checked in Airtable- No error emails received
Click Active toggle in n8n - workflow is now live.
Files are saved to Dropbox with this format:
ExpenseName YYYYMMDD.pdf
Examples:
Office Supplies 20240115.pdfClient Lunch 20240120.pdfSoftware Subscription 20240201.pdf
This ensures:
- Chronological sorting
- Human-readable names
- No duplicate filenames (Date + Expense Name is unique)
To handle more image formats, modify the "Is Image (jpg/png)?" condition:
conditions: [
{ "leftValue": "={{ $json.Receipt[0].type }}", "rightValue": "image/jpeg" },
{ "leftValue": "={{ $json.Receipt[0].type }}", "rightValue": "image/png" },
{ "leftValue": "={{ $json.Receipt[0].type }}", "rightValue": "image/webp" } // Add this
]CloudConvert supports: jpg, png, gif, bmp, webp, tiff, and many more.
Organize by month, client, or category:
// By month
path: "=/Receipts/{{ DateTime.fromISO($('Get Airtable record').item.json.Date).toFormat('yyyy-MM') }}/{{ ... }}"
// By category
path: "=/Receipts/{{ $('Get Airtable record').item.json.Category }}/{{ ... }}"
// By client (if you have a Client field)
path: "=/Receipts/{{ $('Get Airtable record').item.json.Client }}/{{ ... }}"Replace the Dropbox node with:
- Google Drive: Use Google Drive node
- OneDrive: Use Microsoft OneDrive node
- S3: Use AWS S3 node
- SFTP: Use FTP node for self-hosted storage
CloudConvert has usage limits on the free tier. Alternatives:
ImageMagick (self-hosted n8n only):
- Install ImageMagick on your n8n server
- Replace CloudConvert node with Execute Command node:
convert input.jpg -quality 100 output.pdfPython script (self-hosted):
- Use n8n's Code node with Python
- Libraries: PIL (Pillow), img2pdf
To process multiple unsynced expenses at once:
- Replace Webhook trigger with Schedule Trigger
- Add Airtable Search node: Find records where
Submitted = checkedANDSynced to Dropbox = unchecked - Loop through results
This is useful for bulk backfills or if the webhook misses records.
The workflow includes 5 error notification points:
| Failure Point | Email Subject | Likely Cause |
|---|---|---|
| Invalid webhook | Invalid Webhook Data | Airtable automation misconfigured |
| Record not found | Receipt Download Failed | Record deleted before processing |
| Download failed | Receipt Download Failed | Attachment URL expired or invalid |
| Conversion failed | Image Conversion Failed | CloudConvert service down or quota exceeded |
| Upload failed | Dropbox Upload Failed | Dropbox authentication expired or storage full |
| Update failed | Airtable Update Failed | Airtable token expired (file uploaded successfully) |
Each email includes:
- Record ID
- Expense Name
- Date
- Specific failure context
This makes manual intervention straightforward when issues occur.
Webhook not triggering:
- Verify webhook URL is correct in Airtable automation
- Check n8n webhook node is active
- Test webhook manually with Postman/curl
CloudConvert quota exceeded:
- CloudConvert free tier: 25 conversions/day
- Upgrade account or implement daily batch processing
- Alternative: Use ImageMagick for unlimited conversions (self-hosted)
Dropbox upload fails with "invalid path":
- Check Dropbox folder exists
- Ensure Expense Name doesn't contain illegal characters (
/,\,:,*,?,",<,>,|) - Add sanitization in the path expression if needed
Airtable update fails after successful upload:
- File is in Dropbox but record not marked complete
- Check Airtable token permissions
- Manually update record and investigate token expiry
Duplicate files in Dropbox:
- Workflow uses file naming based on Expense Name + Date
- If same expense + date combination exists, Dropbox will append (1), (2), etc.
- Ensure Expense Name + Date combination is unique per receipt
- Monitor error emails daily - Set up email filters/labels for quick review
- Test with all file types - JPG, PNG, PDF before going live
- Backup Airtable regularly - Export expenses monthly as CSV backup
- CloudConvert alternatives - Have ImageMagick ready if you hit API limits
- Webhook security - Consider adding authentication to webhook endpoint
- Rate limiting - Airtable automation may batch webhooks; ensure n8n can handle volume
Free tier limits:
- n8n.cloud: 5,000 workflow executions/month
- CloudConvert: 25 conversions/day
- Airtable: 50,000 records per base
- Dropbox: 2GB storage (Basic plan)
Scaling:
- Self-host n8n for unlimited executions
- CloudConvert paid plan: $9/month for 500 conversions
- Business Airtable for higher limits
- Dropbox Plus: 2TB storage
MIT - Feel free to use, modify, and distribute. Attribution appreciated but not required.
Created by Ken Davis at Lodgepole I/O - Workflow design and operational systems consulting.
This workflow is based on production use for expense tracking and receipt management. It processes hundreds of receipts per month reliably since 2024.
Check out my other n8n templates:
- Email-Based Time Tracking - Automated time logging for invoicing
- More coming soon!