Last updated: March 16, 2026
Managing expense report approvals across distributed finance teams presents unique challenges. When your team spans multiple time zones, waiting for synchronous approvals creates bottlenecks. Employees submit reports and then wait hours—or days—for manager review, delaying reimbursements and creating frustration.
An async approval workflow solves this by establishing clear stages, automated notifications, and explicit response expectations. This guide shows you how to design and implement a remote-friendly expense approval system that keeps money flowing without requiring real-time availability.
Prerequisites
Before you begin, make sure you have the following ready:
- A computer running macOS, Linux, or Windows
- Terminal or command-line access
- Administrator or sudo privileges (for system-level changes)
- A stable internet connection for downloading tools
Step 1: Understand the Approval Pipeline
Before building your workflow, map out the decision points in your expense approval process. Most organizations have several stages:
- Submission: Employee creates and submits the expense report with supporting documentation
- Manager Review: Direct supervisor verifies the expense is legitimate and within policy
- Finance Review: Finance team member validates receipts, categorizes expenses, and prepares payment
- Approval or Rejection: Final sign-off or request for clarification
For remote teams, each stage needs clear ownership, response time expectations, and automated handoffs. Without these elements, expenses stall in inboxes and Slack mentions get lost.
Step 2: Designing Your Workflow Structure
Create a status-based workflow that tracks each expense report through its lifecycle. Here’s a practical schema:
interface ExpenseReport {
id: string;
employeeId: string;
amount: number;
category: ExpenseCategory;
status: ExpenseStatus;
submittedAt: Date;
currentApprover: string;
approvalHistory: ApprovalRecord[];
}
type ExpenseStatus =
| 'draft'
| 'pending_manager'
| 'manager_approved'
| 'pending_finance'
| 'finance_approved'
| 'rejected'
| 'paid';
This structure lets you build automations around status transitions. When an expense moves to pending_manager, the system automatically notifies the appropriate approver and sets an expected response deadline.
Step 3: Implementing Automated Notifications
The key to keeping async workflows moving is timely notifications. Set up triggers that alert approvers when action is needed:
// Example: Notification trigger on status change
function notifyApprover(expenseReport) {
const approver = getApproverForStage(expenseReport.status);
const message = {
channel: approver.slackId,
text: `Expense report #${expenseReport.id} needs your review`,
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Expense Report Review Required*\n\n` +
`Employee: ${expenseReport.employeeName}\n` +
`Amount: $${expenseReport.amount.toFixed(2)}\n` +
`Category: ${expenseReport.category}\n\n` +
`<${expenseReport.url}|Review in Finance Portal>`
}
},
{
type: "actions",
elements: [
{
type: "button",
text: { type: "plain_text", text: "Approve" },
style: "primary",
action_id: "approve_expense",
value: expenseReport.id
},
{
type: "button",
text: { type: "plain_text", text: "Request Info" },
action_id: "request_info_expense",
value: expenseReport.id
}
]
}
]
};
slackClient.chat.postMessage(message);
}
This integration sends a rich message with approve/reject buttons directly to the approver. They can act without leaving their communication tool.
Step 4: Setting Clear Response Time Expectations
Async workflows only work when everyone understands expectations. Define explicit SLAs for each stage:
| Stage | Response Time | Escalation |
|---|---|---|
| Manager Review | 24 hours | Auto-escalate to skip-level after 48 hours |
| Finance Review | 48 hours | Notify finance lead after 72 hours |
| Rejection Resolution | 24 hours | Close ticket after 72 hours of no response |
Program these expectations into your workflow:
function checkApprovalTimeouts() {
const pendingExpenses = db.expenses.find({
status: { $in: ['pending_manager', 'pending_finance'] }
});
for (const expense of pendingExpenses) {
const hoursWaiting = (Date.now() - expense.lastNotificationAt) / 3600000;
const slaHours = expense.status === 'pending_manager' ? 24 : 48;
if (hoursWaiting > slaHours) {
escalateExpense(expense);
}
}
}
Run this check hourly via a scheduled job. When someone misses their SLA, the system escalates to their manager or a backup approver.
Step 5: Build Policy Enforcement
Expense policies exist to ensure compliance, but manually checking every expense is tedious. Build policy rules into your workflow:
const expensePolicy = {
requiresReceipt: 50, // Amount requiring receipt proof
dailyMealLimit: 75,
travelLodgingLimit: 200,
requiresPreapproval: ['conference', 'training', 'travel']
};
function validateExpense(expense) {
const violations = [];
// Check receipt requirement
if (expense.amount >= expensePolicy.requiresReceipt && !expense.receiptUrl) {
violations.push('Receipt required for expenses over $50');
}
// Check category limits
if (expense.category === 'meals' && expense.amount > expensePolicy.dailyMealLimit) {
violations.push(`Meal expense exceeds $${expensePolicy.dailyMealLimit} daily limit`);
}
// Check preapproval requirement
if (expensePolicy.requiresPreapproval.includes(expense.category) && !expense.preapproved) {
violations.push(`${expense.category} expenses require preapproval`);
}
return violations;
}
Run validation when an expense is submitted. If violations exist, reject it immediately with clear feedback rather than letting it progress through the approval pipeline.
Step 6: Create Approval Templates
Standardize your approval requests to help reviewers work efficiently. When employees submit expenses with consistent formatting, approvers can scan reports quickly:
### Step 7: Expense Report #{{id}}
**Employee:** {{employee_name}}
**Date:** {{submission_date}}
**Total Amount:** ${{total_amount}}
### Expenses
| Date | Category | Amount | Notes |
|------|----------|--------|-------|
| {{date}} | {{category}} | ${{amount}} | {{notes}} |
### Attachments
- [Receipt.pdf]({{receipt_url}})
- [Additional Documentation]({{docs_url}})
**Policy Compliance:** {{compliance_status}}
Provide this template through your expense submission form so employees know what information approvers need.
Step 8: Handling Rejections and Appeals
Rejections frustrate employees, especially when feedback is vague. Structure rejection responses:
function rejectExpense(expense, approver, reason) {
const policyReference = getPolicySection(reason.category);
expense.status = 'rejected';
expense.rejection = {
by: approver.id,
reason: reason.category,
details: reason.details,
policyReference: policyReference.url,
correctedAt: null
};
notifyEmployee(expense, {
subject: `Expense Report #${expense.id} Needs Revision`,
body: `Your expense report was not approved. ${reason.details}\n\n` +
`See policy: ${policyReference.url}\n\n` +
`To resubmit, correct the issue and submit again.`
});
}
When approvers select from standardized rejection reasons, the system provides policy context automatically. Employees understand what went wrong and how to fix it.
Step 9: Measuring Workflow Performance
Track metrics to continuously improve your process:
- Cycle Time: Total time from submission to payment
- Approval Rate: Percentage approved on first submission
- Rejection Rate: Percentage requiring revision
- Bottleneck Identification: Which stages cause delays
- Approver Load: Distribution of reviews across team members
function generateWeeklyReport() {
const stats = {
totalSubmitted: db.expenses.count({ submittedAt: { $gte: weekAgo } }),
avgCycleTime: calculateAverage('submittedAt', 'paidAt'),
firstPassApprovalRate: db.expenses.count({
submittedAt: { $gte: weekAgo },
rejectionCount: 0
}) / db.expenses.count({ submittedAt: { $gte: weekAgo } })
};
sendToFinanceLead(stats);
}
Review these metrics weekly. If approval times spike, investigate whether team capacity or policy confusion is causing delays.
Troubleshooting
Configuration changes not taking effect
Restart the relevant service or application after making changes. Some settings require a full system reboot. Verify the configuration file path is correct and the syntax is valid.
Permission denied errors
Run the command with sudo for system-level operations, or check that your user account has the necessary permissions. On macOS, you may need to grant terminal access in System Settings > Privacy & Security.
Connection or network-related failures
Check your internet connection and firewall settings. If using a VPN, try disconnecting temporarily to isolate the issue. Verify that the target server or service is accessible from your network.
Frequently Asked Questions
How long does it take to set up remote finance team approval workflow?
For a straightforward setup, expect 30 minutes to 2 hours depending on your familiarity with the tools involved. Complex configurations with custom requirements may take longer. Having your credentials and environment ready before starting saves significant time.
What are the most common mistakes to avoid?
The most frequent issues are skipping prerequisite steps, using outdated package versions, and not reading error messages carefully. Follow the steps in order, verify each one works before moving on, and check the official documentation if something behaves unexpectedly.
Do I need prior experience to follow this guide?
Basic familiarity with the relevant tools and command line is helpful but not strictly required. Each step is explained with context. If you get stuck, the official documentation for each tool covers fundamentals that may fill in knowledge gaps.
Will this work with my existing CI/CD pipeline?
The core concepts apply across most CI/CD platforms, though specific syntax and configuration differ. You may need to adapt file paths, environment variable names, and trigger conditions to match your pipeline tool. The underlying workflow logic stays the same.
Where can I get help if I run into issues?
Start with the official documentation for each tool mentioned. Stack Overflow and GitHub Issues are good next steps for specific error messages. Community forums and Discord servers for the relevant tools often have active members who can help with setup problems.
Related Articles
- Remote Team Handbook Section Template for Writing Expense Re
- Best Client Approval Workflow Tool for Remote Design Teams
- Best Deploy Workflow for a Remote Infrastructure Team of 3
- Remote Team Charter Template Guide 2026
- Best Expense Management Platform for Remote Teams with Recei Built by theluckystrike — More at zovo.one