1 Executive Summary
Missio ERP has a fully functional employee/HR module, time management system, and fund-based budget/accounting system. The platform currently integrates with QuickBooks Online for invoice/payment sync and has an ERPNext integration in progress for General Ledger and finance. However, no payroll processing engine exists — the payroll, position control, and benefits pages are static demo UIs with hardcoded data.
Recommendation
Integrate Gusto Embedded Payroll API as the payroll calculation engine. Missio remains the system of record for employee data, time/attendance, and position management. Gusto handles all tax calculations, direct deposit, compliance filings, and benefits administration.
Missio ERP (Source of Truth)
- Employee master data
- Time & attendance
- Position control
- Salary schedules (K-12 step/lane)
- Approval workflows
- Budget & fund accounting
- Reporting & dashboards
Gusto API
- Payroll calculation engine
- Tax withholding (fed/state/FICA)
- Direct deposit & ACH
- W-2 / 941 / tax filing
- Benefits administration
- Garnishments
- Workers' compensation
QuickBooks Online (Existing)
- Invoice sync (existing)
- Payment sync (existing)
- Payroll journal entries (new)
ERPNext (In Progress)
- General Ledger
- Chart of Accounts
- Financial reporting
- Payroll journal entries (future)
Why Gusto
| Factor | Gusto | ADP Workforce Now |
|---|---|---|
| API quality | Modern REST, well-documented, embedded SDK | SOAP/REST hybrid, complex onboarding |
| K-12 support | Multi-EIN, multi-location, multi-pay-schedule | Better for very large enterprise |
| Integration effort | 4-6 weeks | 8-12 weeks |
| Cost per employee | ~$6-12/mo | ~$15-25/mo |
| Benefits admin | Built-in | Separate module |
| Tax filing | All 50 states + federal | All 50 states + federal |
| Embedded React SDK | Full payroll UI components | Not available |
| API version | v2026-02-01 (latest) | Varies |
2 Integration Architecture
System Ownership Model
| Data / Process | Owner | Direction | Sync Trigger |
|---|---|---|---|
| Employee profile (name, email, DOB, address) | Missio | Missio → Gusto | Real-time (Observer) |
| SSN / bank account / direct deposit | Gusto | Entered directly in Gusto | Employee onboarding |
| Job title, department, work location | Missio | Missio → Gusto | Real-time (Observer) |
| Compensation rate (salary/hourly) | Missio | Missio → Gusto | On salary placement change |
| Time & attendance hours | Missio | Missio → Gusto | Per payroll run |
| Leave / PTO balances | Missio | Missio → Gusto | Per payroll run |
| Tax calculation (fed/state/FICA/Medicare) | Gusto | Gusto → Missio | After payroll calculate |
| Deductions (benefits, garnishments) | Gusto | Gusto → Missio | After payroll calculate |
| Net pay & direct deposit | Gusto | Gusto → Missio | After payroll submit |
| Pay stubs / W-2 forms | Gusto | Gusto → Missio (display) | After payroll processed |
| GL journal entries | Missio | Missio → ERPNext/QB | After payroll results received |
| Benefits enrollment | Gusto | Gusto → Missio | Open enrollment / life event |
| W-4 / state tax elections | Gusto | Gusto managed | Employee self-service |
gusto_employee_id reference UUID. This eliminates PCI/PII liability from the Missio database.
3 Gusto API Authentication
3.1 Base URLs
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://api.gusto-demo.com | Development & testing |
| Production | https://api.gusto.com | Live payroll processing |
3.2 API Versioning
Gusto uses date-based API versioning via the X-Gusto-API-Version header. Current version: 2026-02-01. Available versions: 2024-03-01, 2024-04-01, 2025-06-15, 2025-11-15, 2026-02-01.
3.3 Token Types
| Token Type | Scope | Expiration | Use Case |
|---|---|---|---|
| System Access Token | Application-level | 7200s (2 hours) | Creating companies, viewing invoices, managing webhooks |
| Company Access Token | Company-specific | 7200s (2 hours) | Managing employees, running payroll, benefits |
3.4 System Access Token Request
{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "system_access"
}
{
"access_token": "system_level_token_here",
"token_type": "Bearer",
"created_at": 1712345678,
"expires_in": 7200
}
3.5 Company Access Token Request (Refresh)
{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "refresh_token",
"refresh_token": "previously_stored_refresh_token"
}
{
"access_token": "new_company_access_token",
"refresh_token": "new_refresh_token", // Previous refresh token is revoked
"company_uuid": "abc-123-def-456",
"expires_in": 7200
}
company_uuid, access_token, refresh_token, and access_token_expiration in the database. Refresh 60 seconds before expiration. Lock the database row during refresh to prevent race conditions from concurrent requests.
3.6 Required OAuth Scopes
| Scope | Access | Used For |
|---|---|---|
| employees:read | Read | Fetching employee list and details |
| employees:write | Write | Creating/updating employees |
| compensations:read | Read | Reading salary/rate data (sensitive) |
| compensations:write | Write | Setting compensation rates |
| payrolls:read | Read | Viewing payroll data and results |
| payrolls:run | Write | Creating, calculating, submitting payroll |
| employee_benefits:read | Read | Benefits enrollment data |
| employee_benefits:write | Write | Managing benefits enrollment |
| employee_benefits:read:phi | Read | Benefits with PHI visibility |
| company_benefits:read | Read | Company benefit plan configuration |
| company_benefits:write | Write | Creating/modifying benefit plans |
| pay_schedules:read | Read | Pay schedule and period data |
| pay_schedules:write | Write | Creating/modifying pay schedules |
| webhook_subscriptions:write | Write | Managing webhook subscriptions |
| contractors:read | Read | 1099 contractor data |
| contractors:write | Write | Managing contractors |
| contractor_payments:read | Read | Contractor payment data |
4 Gusto API Endpoints — Full Reference
Below is the complete list of Gusto API endpoints we need to integrate, organized by domain. Base URL: https://api.gusto.com/v1
4.1 Company Setup
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/partner-managed-companies | Create partner-managed company for CSD | One-time setup |
| GET | /v1/companies/{company_id} | Get company details, onboarding status | Setup verification |
| PUT | /v1/companies/{company_id} | Update company info (EIN, address) | As needed |
| GET | /v1/companies/{company_id}/onboarding_status | Check company setup progress | During onboarding |
| PUT | /v1/companies/{company_id}/finish_onboarding | Mark company onboarding complete | One-time |
| GET | /v1/companies/{company_id}/custom_fields | Get custom fields for employee data | Setup |
4.2 Locations
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/locations | Create school/building locations | Setup per school |
| GET | /v1/companies/{company_id}/locations | List all locations | Sync |
| PUT | /v1/locations/{location_id} | Update location details | As needed |
| GET | /v1/locations/{location_id}/minimum_wages | Get minimum wage for location | Compliance check |
4.3 Departments
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/departments | Create department (mirrors Missio Team) | Setup & sync |
| GET | /v1/companies/{company_id}/departments | List all departments | Sync verification |
| PUT | /v1/departments/{department_id} | Update department name | On Team change |
| DELETE | /v1/departments/{department_id} | Remove department | On Team delete |
4.4 Employees (W-2)
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/employees | Create new employee | EmployeeObserver::created() |
| GET | /v1/companies/{company_id}/employees | List all employees (active, terminated, onboarding) | Bulk sync / reconciliation |
| GET | /v1/employees/{employee_id} | Get single employee details | Sync verification |
| PUT | /v1/employees/{employee_id} | Update employee info | EmployeeObserver::updated() |
| DELETE | /v1/employees/{employee_id} | Remove onboarding employee | If hire cancelled |
4.5 Employee Onboarding & Compliance
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/employees/{employee_id}/onboarding_status | Check employee setup progress | Dashboard display |
| PUT | /v1/employees/{employee_id}/onboarding_status | Update onboarding step | During onboarding |
| GET | /v1/employees/{employee_id}/federal_taxes | Get W-4 data | Display/sync |
| PUT | /v1/employees/{employee_id}/federal_taxes | Update W-4 withholding | Employee self-service |
| GET | /v1/employees/{employee_id}/state_taxes | Get GA state tax elections | Display/sync |
| PUT | /v1/employees/{employee_id}/state_taxes | Update GA state withholding | Employee self-service |
| PUT | /v1/employees/{employee_id}/i9_authorization | Update I-9 work authorization | During onboarding |
| GET | /v1/employees/{employee_id}/employment_history | Get employment history | Reporting |
4.6 Employee Addresses
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/employees/{employee_id}/home_addresses | Add home address | Employee create/update |
| PUT | /v1/home_addresses/{address_id} | Update home address | Address change |
| POST | /v1/employees/{employee_id}/work_addresses | Assign work location (school) | Employee assignment |
| PUT | /v1/work_addresses/{address_id} | Update work location | Transfer |
4.7 Jobs & Compensations
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/employees/{employee_id}/jobs | Create job assignment (title, dept) | On hire / transfer |
| GET | /v1/employees/{employee_id}/jobs | List employee jobs | Sync |
| PUT | /v1/jobs/{job_id} | Update job title/dept | On change |
| POST | /v1/jobs/{job_id}/compensations | Set compensation (rate, payment_unit) | Salary placement |
| GET | /v1/jobs/{job_id}/compensations | Get compensation history | Display |
| PUT | /v1/compensations/{compensation_id} | Update rate (step/lane change) | Annual increment |
Hour — hourly rate, Week — weekly salary, Month — monthly salary, Year — annual salary, Paycheck — per-paycheck amount. For K-12 salaried employees, use Year with the annual salary from the step/lane schedule.
4.8 Terminations & Rehires
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/employees/{employee_id}/terminations | Terminate employee | On separation |
| GET | /v1/employees/{employee_id}/terminations | Get termination details | Final pay processing |
| DELETE | /v1/employees/{employee_id}/terminations | Cancel termination | If reversed |
| POST | /v1/employees/{employee_id}/rehire | Rehire former employee | Rehire |
4.9 Pay Schedules & Pay Periods
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/pay_schedules | Create pay schedule (semi-monthly, biweekly) | One-time setup |
| GET | /v1/companies/{company_id}/pay_schedules | List pay schedules | Dashboard |
| PUT | /v1/pay_schedules/{pay_schedule_id} | Update schedule | Configuration |
| GET | /v1/companies/{company_id}/pay_schedules/preview | Preview future pay dates | Calendar setup |
| POST | /v1/companies/{company_id}/pay_schedules/assignments | Assign employees to schedule | Employee setup |
| GET | /v1/companies/{company_id}/pay_schedules/assignments | Get schedule assignments | Sync |
| GET | /v1/companies/{company_id}/pay_periods | List pay periods with dates | Payroll dashboard |
| GET | /v1/companies/{company_id}/unprocessed_termination_pay_periods | Get final pay periods for termed employees | Final pay processing |
4.10 Earning Types
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/companies/{company_id}/earning_types | List all earning types (default + custom) | Setup & mapping |
| POST | /v1/companies/{company_id}/earning_types | Create custom earning type (stipend, coaching) | K-12 setup |
| PUT | /v1/companies/{company_id}/earning_types/{earning_type_id} | Update earning type | Configuration |
4.11 Payroll Operations (Core)
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/companies/{company_id}/payrolls | List payrolls (filter by status, date range, type) | Dashboard |
| GET | /v1/companies/{company_id}/payrolls/{payroll_id} | Get single payroll with employee compensations | After calculate/process |
| POST | /v1/companies/{company_id}/payrolls | Create off-cycle payroll (bonus, correction) | Off-cycle runs |
| PUT | /v1/companies/{company_id}/payrolls/{payroll_id} | Update payroll with employee hours/earnings | Before calculation |
| PUT | /v1/companies/{company_id}/payrolls/{payroll_id}/calculate | Calculate taxes, deductions, net pay | Preview step (async, returns 202) |
| PUT | /v1/companies/{company_id}/payrolls/{payroll_id}/submit | Submit payroll for processing & payment | Final submission (async, returns 202) |
| PUT | /v1/companies/{company_id}/payrolls/{payroll_id}/cancel | Cancel unprocessed payroll | If voided |
| PUT | /v1/companies/{company_id}/payrolls/{payroll_id}/prepare | Prepare payroll for editing | Before update |
| GET | /v1/companies/{company_id}/payrolls/{payroll_id}/payroll-blockers | Get issues preventing submission | Pre-submit validation |
| GET | /v1/companies/{company_id}/payroll_reversals | List payroll reversals | Audit |
| POST | /v1/companies/{company_id}/payrolls/{payroll_id}/gross-up | Calculate gross-up (bonus after tax) | Bonus payroll |
| POST | /v1/companies/{company_id}/payrolls/skip | Skip a pay period | Holiday / school break |
4.12 Pay Stubs & Receipts
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/companies/{company_id}/employees/{employee_id}/pay_stubs | List pay stubs for employee | Employee self-service |
| GET | /v1/companies/{company_id}/payrolls/{payroll_id}/employees/{employee_id}/pay_stub | Get single pay stub (PDF) | View/print |
| GET | /v1/companies/{company_id}/payment_receipts/payrolls/{payroll_id} | Get payroll payment receipt | Finance records |
| POST | /v1/companies/{company_id}/payrolls/{payroll_id}/generated_documents/printable_payroll_checks | Generate printable checks | Paper check processing |
4.13 Benefits Administration
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/benefits | List all supported benefit types | Setup |
| POST | /v1/companies/{company_id}/company_benefits | Create company benefit plan | Plan setup |
| GET | /v1/companies/{company_id}/company_benefits | List company benefit plans | Dashboard |
| PUT | /v1/company_benefits/{benefit_id} | Update benefit plan | Rate changes |
| DELETE | /v1/company_benefits/{benefit_id} | Deactivate benefit plan | Plan termination |
| POST | /v1/employees/{employee_id}/employee_benefits | Enroll employee in benefit | Open enrollment / new hire |
| GET | /v1/employees/{employee_id}/employee_benefits | List employee enrollments | Display |
| PUT | /v1/employee_benefits/{employee_benefit_id} | Update enrollment (coverage level) | Life event / open enrollment |
| DELETE | /v1/employee_benefits/{employee_benefit_id} | Remove enrollment | Disenrollment |
4.14 Garnishments
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/employees/{employee_id}/garnishments | Create garnishment (child support, tax levy) | Court order |
| GET | /v1/employees/{employee_id}/garnishments | List employee garnishments | Display |
| PUT | /v1/garnishments/{garnishment_id} | Update garnishment | Amount change |
| GET | /v1/employees/{employee_id}/garnishments/child_support | Get child support details | Compliance |
4.15 1099 Contractors
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/contractors | Create contractor | Vendor onboarding |
| GET | /v1/companies/{company_id}/contractors | List contractors | Sync with Missio vendors |
| PUT | /v1/contractors/{contractor_id} | Update contractor | Profile change |
| POST | /v1/companies/{company_id}/contractor_payments | Create contractor payment | Invoice payment |
| GET | /v1/companies/{company_id}/contractor_payments | List contractor payments | 1099 reporting |
4.16 Time Off Policies
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/time_off_policies | Create PTO/sick policy | Setup |
| GET | /v1/companies/{company_id}/time_off_policies | List time off policies | Sync |
| PUT | /v1/time_off_policies/{policy_id}/balance | Adjust employee PTO balance | Missio leave sync |
| PUT | /v1/time_off_policies/{policy_id}/add_employees | Enroll employees in policy | New hire |
| PUT | /v1/time_off_policies/{policy_id}/remove_employees | Remove employee from policy | Termination |
| GET | /v1/employees/{employee_id}/time_off_activities | Get PTO history & accruals | Dashboard |
4.17 Tax & Compliance
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/companies/{company_id}/federal_tax_details | Get company federal tax setup | Compliance audit |
| PUT | /v1/companies/{company_id}/federal_tax_details | Update federal tax details | Configuration |
| GET | /v1/companies/{company_id}/tax_requirements | Get state tax requirements (GA) | Compliance |
| PUT | /v1/companies/{company_id}/tax_requirements | Fulfill tax requirements | State registration |
| GET | /v1/companies/{company_id}/tax_liabilities | Get tax liabilities | Reporting |
4.18 Forms & Documents
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| GET | /v1/companies/{company_id}/forms | List company forms (941, W-3) | Year-end |
| GET | /v1/employees/{employee_id}/forms | List employee forms (W-2) | Year-end self-service |
| GET | /v1/generated_documents/{document_type}/{document_id} | Download generated document | Form retrieval |
| POST | /v1/sandbox/companies/{company_id}/generate_w2 | Generate test W-2 (sandbox only) | Testing |
4.19 Reporting & General Ledger
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/reports | Generate custom report | Reporting |
| GET | /v1/companies/{company_id}/reports/{report_id} | Get generated report data | Report retrieval |
| POST | /v1/companies/{company_id}/general_ledger | Generate GL report with debits/credits | After each payroll for GL posting |
| GET | /v1/companies/{company_id}/report_templates/{report_type} | Get report template structure | Setup |
| POST | /v1/companies/{company_id}/reports/employees_annual_fica_wage | FICA wage report | Year-end |
POST /general_ledger endpoint returns payroll journal entries with debits and credits by account category. This is the primary data source for posting payroll to ERPNext/QuickBooks. We map Gusto GL accounts to Missio fund_code + account_code and generate BudgetTransaction records.
4.20 Webhooks
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/webhook_subscriptions | Create webhook subscription | One-time setup |
| GET | /v1/webhook_subscriptions | List webhook subscriptions | Admin |
| PUT | /v1/webhook_subscriptions/{subscription_id} | Update subscription URL/events | Configuration |
| DELETE | /v1/webhook_subscriptions/{subscription_id} | Remove subscription | Cleanup |
| PUT | /v1/webhook_subscriptions/{subscription_id}/verify | Verify webhook endpoint | During setup |
| GET | /v1/webhook_subscriptions/health_check | Check webhook health | Monitoring |
4.21 Bank Accounts & Payment Methods
| Method | Endpoint | Purpose | When Called |
|---|---|---|---|
| POST | /v1/companies/{company_id}/bank_accounts | Add company bank account for payroll funding | Setup |
| PUT | /v1/companies/{company_id}/bank_accounts/{bank_id}/verify | Verify bank account (micro-deposits) | Setup |
| POST | /v1/employees/{employee_id}/bank_accounts | Add employee bank account for direct deposit | Employee self-service |
| GET | /v1/employees/{employee_id}/payment_method | Get employee payment method | Display |
| PUT | /v1/employees/{employee_id}/payment_method | Set payment method (direct deposit/check) | Employee setup |
| GET | /v1/companies/{company_id}/payment_configs | Get payment configuration | Setup |
5 Data Field Mapping (Missio → Gusto)
5.1 Employee Create/Update Mapping
| Missio Field | Missio Source | Gusto Field | Notes |
|---|---|---|---|
| user.name (split) | User.name | first_name, last_name | Parse first/last from full name |
| user.email | User.email | email | Primary email |
| details.date_of_birth | EmployeeDetails.date_of_birth | date_of_birth | Format: YYYY-MM-DD |
| details.address | EmployeeDetails.address | home_address.street_1 | Parse street, city, state, zip |
| details.joining_date | EmployeeDetails.joining_date | start_date | Hire date |
| user.mobile | User.mobile | phone | Optional |
| SSN | Not in Missio | ssn | Entered directly in Gusto during onboarding |
5.2 Job & Compensation Mapping
| Missio Field | Missio Source | Gusto Field | Notes |
|---|---|---|---|
| Designation name | Designation.name | job.title | Job title text |
| Department name | Team.team_name | department (UUID) | Must create dept in Gusto first, map by name |
| School/building | CompanyAddress | work_address (location UUID) | Must create location in Gusto first |
| Salary (annual) | EmployeeSalaryPlacement.total_salary | compensation.rate | String decimal (e.g., "52450.00") |
| Pay type | EmployeeSalaryPlacement.pay_type | compensation.payment_unit | "Year" for salary, "Hour" for hourly |
| Hourly rate | EmployeeDetails.hourly_rate | compensation.rate | For hourly employees only |
| FLSA status | Derived from employment_type | compensation.flsa_status | "Exempt" for salaried certified, "Nonexempt" for hourly |
5.3 Time & Attendance → Payroll Mapping
| Missio Data | Missio Source | Gusto Payroll Field | Aggregation |
|---|---|---|---|
| Regular hours | ProjectTimeLog (approved=true) | hourly_compensations[].hours (Regular) | Sum by employee for pay period, cap at 40hr/week |
| Overtime hours | ProjectTimeLog (approved=true) | hourly_compensations[].hours (Overtime) | Hours exceeding 40hr/week threshold |
| PTO hours used | Leave (status=approved, paid=true) | paid_time_off[].hours (Vacation) | Sum approved paid leave in pay period |
| Sick hours used | Leave (type=sick, approved) | paid_time_off[].hours (Sick) | Sum approved sick leave in pay period |
| Stipends | EmployeeSalaryPlacement.stipends | fixed_compensations[].amount | Per-paycheck prorated amount |
5.4 Department & Location Mapping
| Missio Entity | Missio Model | Gusto Entity | Mapping Key |
|---|---|---|---|
| Department | Team | Department | Store gusto_department_id on Team or mapping table |
| School/Building | CompanyAddress | Location | Store gusto_location_id on CompanyAddress |
| Pay schedule | PayCalendar (new) | PaySchedule | Store gusto_pay_schedule_id on PayCalendar |
6 Payroll Run Lifecycle — Step-by-Step API Flow
Blue = Missio Orange = Gusto API (async)
Step 1: Get Unprocessed Payrolls from Gusto
GET /v1/companies/{company_id}/payrolls?processing_statuses=unprocessed
Authorization: Bearer {company_access_token}
X-Gusto-API-Version: 2026-02-01
[
{
"payroll_id": "pay_abc123",
"pay_period": {
"start_date": "2026-04-01",
"end_date": "2026-04-15",
"pay_schedule_id": "ps_xyz789"
},
"check_date": "2026-04-20",
"processing_status": "unprocessed",
"employee_compensations": [
{
"employee_id": "emp_001",
"fixed_compensations": [
{ "name": "Regular", "amount": "0.00", "job_id": "job_123" }
],
"hourly_compensations": [
{ "name": "Regular", "hours": "0.000", "job_id": "job_123", "compensation_multiplier": 1 },
{ "name": "Overtime", "hours": "0.000", "job_id": "job_123", "compensation_multiplier": 1.5 }
],
"paid_time_off": [
{ "name": "Vacation Hours", "hours": "0.000" },
{ "name": "Sick Hours", "hours": "0.000" }
]
}
// ... one object per employee
]
}
]
Step 2: Aggregate Approved Hours from Missio
The TimesheetAggregationService queries Missio's time tracking data:
// TimesheetAggregationService.php
public function aggregateForPayPeriod($startDate, $endDate): Collection
{
return ProjectTimeLog::query()
->where('approved', true)
->whereBetween('start_time', [$startDate, $endDate])
->select('user_id')
->selectRaw('SUM(total_minutes) / 60 as total_hours')
->groupBy('user_id')
->get()
->map(function($row) {
return [
'employee_id' => $row->user->employeeDetail->gusto_employee_id,
'regular_hours' => min($row->total_hours, 80), // 80 for semi-monthly
'overtime_hours' => max($row->total_hours - 80, 0),
];
});
}
// Also aggregate approved leaves
public function aggregateLeavesForPayPeriod($startDate, $endDate): Collection
{
return Leave::query()
->where('status', 'approved')
->where('paid', true)
->whereBetween('leave_date', [$startDate, $endDate])
->with('type')
->get()
->groupBy('user_id');
}
Step 3: Update Payroll with Employee Hours/Earnings
PUT /v1/companies/{company_id}/payrolls/{payroll_id}
Authorization: Bearer {company_access_token}
Content-Type: application/json
{
"employee_compensations": [
{
"employee_id": "emp_001",
"fixed_compensations": [
{
"name": "Coaching Stipend",
"amount": "250.00",
"job_id": "job_123"
}
],
"hourly_compensations": [
{
"name": "Regular",
"hours": "80.000",
"job_id": "job_123"
},
{
"name": "Overtime",
"hours": "4.500",
"job_id": "job_123"
}
],
"paid_time_off": [
{
"name": "Vacation Hours",
"hours": "8.000"
},
{
"name": "Sick Hours",
"hours": "0.000"
}
]
}
// ... repeat for all employees in this payroll
]
}
Step 4: Calculate Payroll (Async)
PUT /v1/companies/{company_id}/payrolls/{payroll_id}/calculate
Authorization: Bearer {company_access_token}
// Returns 202 Accepted - Gusto calculates taxes, deductions, net pay asynchronously
// Poll GET /payrolls/{payroll_id}?include=taxes,benefits,deductions to get results
GET /v1/companies/{company_id}/payrolls/{payroll_id}?include=taxes,benefits,deductions
// Response includes calculated employee_compensations with:
{
"payroll_id": "pay_abc123",
"processing_status": "calculated",
"totals": {
"company_debit": "156432.87",
"net_pay_debit": "112345.67",
"tax_debit": "31208.45",
"reimbursement_debit": "1250.00",
"child_support_debit": "450.00",
"benefits_debit": "11178.75"
},
"employee_compensations": [
{
"employee_id": "emp_001",
"gross_pay": "4500.00",
"net_pay": "3245.67",
"taxes": [
{ "name": "Federal Income Tax", "amount": "540.00", "employer": false },
{ "name": "Social Security", "amount": "279.00", "employer": false },
{ "name": "Social Security", "amount": "279.00", "employer": true },
{ "name": "Medicare", "amount": "65.25", "employer": false },
{ "name": "Medicare", "amount": "65.25", "employer": true },
{ "name": "Georgia Income Tax", "amount": "225.00", "employer": false }
],
"benefits": [
{ "name": "Health Insurance", "employee_deduction": "42.00", "company_contribution": "840.00" },
{ "name": "Georgia TRS", "employee_deduction": "270.00", "company_contribution": "936.00" }
],
"deductions": [
{ "name": "403(b)", "amount": "225.00" }
]
}
]
}
Step 5-6: Review & Approve (Missio Internal)
Payroll manager reviews calculated results in Missio dashboard. If approved, triggers the existing ApprovalController workflow. Payroll run status transitions to approved.
Step 7: Submit Payroll for Processing (Async)
PUT /v1/companies/{company_id}/payrolls/{payroll_id}/submit
Authorization: Bearer {company_access_token}
// Returns 202 Accepted
// Gusto initiates ACH transfers and direct deposits
// Listen for payroll.paid webhook or poll for processing_status = "processed"
/calculate and /submit return HTTP 202 and process asynchronously. Missio must either poll the GET endpoint or listen for the payroll.paid webhook to confirm completion. Errors (422) indicate payroll blockers that must be resolved before resubmission.
Step 8: Post to GL
After payroll is processed, Missio generates journal entries. See Section 8 for full GL posting flow.
7 Webhook Integration
7.1 Webhook Setup Flow
- Create an HTTPS POST endpoint in Missio:
/api/gusto/webhooks - Create webhook subscription via API with desired event types
- Gusto sends
verification_tokenPOST to our endpoint - Call
PUT /webhook_subscriptions/{id}/verifywith the token - Implement HMAC-SHA256 signature verification using
verification_tokenas secret - Compare computed hash against
X-Gusto-Signaturerequest header
7.2 Webhook Payload Structure
{
"uuid": "evt_abc123",
"event_type": "payroll.paid",
"resource_type": "Company",
"resource_uuid": "company_uuid_here",
"entity_type": "Payroll",
"entity_uuid": "payroll_uuid_here",
"timestamp": 1712345678
}
// Header: X-Gusto-Signature: hmac_sha256_hash
7.3 Events to Subscribe
| Event Type | Triggered When | Missio Action |
|---|---|---|
payroll.paid | Payroll processed, ACH initiated | Fetch payroll results → store paychecks → generate GL entries → post to ERPNext/QB |
employee.created | Employee created in Gusto | Update gusto_employee_id in Missio (confirmation) |
employee.updated | Employee profile changed in Gusto | Sync changes back to Missio if needed |
employee.job_compensation.updated | Compensation changed | Log for audit trail |
employee.benefit.updated | Benefits enrollment changed | Update benefits display in Missio |
employee.terminated | Employee terminated | Update employee status in Missio |
company.bank_account.verified | Bank account verified | Update setup status |
form.available | W-2 or 941 ready | Notify employee / show in portal |
contractor_payment.processed | Contractor payment sent | Update 1099 tracking in Missio |
7.4 Retry Policy
Gusto retries webhook delivery up to 16 times over 3 days using exponential backoff. Missio endpoint must return 2xx within 10 seconds. Queue heavy processing as Laravel jobs.
7.5 Laravel Webhook Handler
// app/Http/Controllers/Api/GustoWebhookController.php
public function handle(Request $request): JsonResponse
{
// 1. Verify HMAC signature
$signature = $request->header('X-Gusto-Signature');
$computed = hash_hmac('sha256', $request->getContent(), config('gusto.webhook_secret'));
if (!hash_equals($computed, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
// 2. Dispatch to appropriate handler based on event_type
match ($request->input('event_type')) {
'payroll.paid' => FetchPayrollResults::dispatch($request->input('entity_uuid')),
'employee.created' => SyncEmployeeFromGusto::dispatch($request->input('entity_uuid')),
'employee.updated' => SyncEmployeeFromGusto::dispatch($request->input('entity_uuid')),
'employee.terminated' => HandleGustoTermination::dispatch($request->input('entity_uuid')),
'form.available' => NotifyFormAvailable::dispatch($request->all()),
default => Log::info('Unhandled Gusto webhook', $request->all()),
};
return response()->json(['status' => 'ok']);
}
8 GL Journal Entry Flow (Gusto → Missio → ERPNext/QB)
8.1 Flow Overview
8.2 Gusto General Ledger Report
After each payroll is processed, call the Gusto GL endpoint to get debits/credits by account:
POST /v1/companies/{company_id}/general_ledger
{
"start_date": "2026-04-01",
"end_date": "2026-04-15"
}
// Returns journal entries with:
// - Account categories (Wages, Taxes, Benefits, etc.)
// - Debit and credit amounts
// - Per-employee breakdown
8.3 Fund Split Example
Employee Jane Smith: 60% General Fund (100), 40% Title I (200).
Gross Pay: $4,500.00 semi-monthly.
| Entry Type | Fund Code | Account Code | Debit | Credit |
|---|---|---|---|---|
| Salary Expense | 100 (General) | 6110 (Cert Salaries) | $2,700.00 | |
| Salary Expense | 200 (Title I) | 6110 (Cert Salaries) | $1,800.00 | |
| Federal Tax Payable | 100 | 2110 | $324.00 | |
| Federal Tax Payable | 200 | 2110 | $216.00 | |
| GA State Tax Payable | 100 | 2120 | $135.00 | |
| GA State Tax Payable | 200 | 2120 | $90.00 | |
| FICA (Employee) | 100 | 2130 | $167.40 | |
| FICA (Employee) | 200 | 2130 | $111.60 | |
| Medicare (Employee) | 100 | 2140 | $39.15 | |
| Medicare (Employee) | 200 | 2140 | $26.10 | |
| Net Pay (Cash) | 100 | 1010 | $1,231.47 | |
| Net Pay (Cash) | 200 | 1010 | $820.98 | |
| Employer FICA | 100 | 6210 | $167.40 | |
| Employer FICA | 200 | 6210 | $111.60 | |
| Employer Medicare | 100 | 6220 | $39.15 | |
| Employer Medicare | 200 | 6220 | $26.10 |
Yellow-highlighted rows = employer cost entries. Each entry updates the corresponding BudgetAllocation.expended_amount.
8.4 Account Code Mapping
| Missio account_code | Description | Type | Gusto GL Category |
|---|---|---|---|
6110 | Certified Salaries | Expense (Debit) | Wages & Salaries |
6120 | Classified Salaries | Expense (Debit) | Wages & Salaries |
6210 | Employer FICA/Medicare | Expense (Debit) | Employer Taxes |
6220 | Employer Benefits | Expense (Debit) | Benefits |
6230 | Employer Retirement (TRS) | Expense (Debit) | Benefits |
2110 | Federal Tax Payable | Liability (Credit) | Employee Taxes |
2120 | State Tax Payable | Liability (Credit) | Employee Taxes |
2130 | FICA Payable | Liability (Credit) | Employee + Employer Taxes |
2140 | Medicare Payable | Liability (Credit) | Employee + Employer Taxes |
2150 | Benefits Payable | Liability (Credit) | Benefits |
1010 | Cash – Payroll | Asset (Credit) | Net Pay |
9 New Laravel Code — Services, Jobs, Controllers
9.1 New Directory Structure
9.2 Existing Files to Modify
| File | Change | Type |
|---|---|---|
app/Models/EmployeeDetails.php | Add relationships: position(), salaryPlacement(), fundAllocations(), payCalendar(). Add gusto_employee_id, gusto_sync_status fields. | Modify |
app/Models/Team.php | Add gusto_department_id field | Modify |
app/Models/CompanyAddress.php | Add gusto_location_id field | Modify |
app/Observers/EmployeeDetailObserver.php | Dispatch SyncEmployeeToGusto job on create/update | Modify |
app/Http/Controllers/ApprovalController.php | Add PayrollRun as approvable entity type | Extend |
app/Http/Controllers/QuickbookController.php | Add createPayrollJournalEntry() method | Extend |
routes/budget.php | Replace demo routes with real controller routes | Modify |
resources/views/sections/menu.blade.php | Add Payroll sub-menu items | Modify |
resources/views/employees/show.blade.php | Add "Compensation" tab | Modify |
app/Console/Kernel.php | Schedule Gusto sync & reconciliation jobs | Modify |
routes/api.php | Add Gusto webhook route | Modify |
9.3 GustoClient.php — Core HTTP Client
namespace App\Services\Gusto;
use Illuminate\Support\Facades\Http;
use App\Models\GustoSetting;
class GustoClient
{
private GustoSetting $settings;
private string $baseUrl;
public function __construct()
{
$this->settings = GustoSetting::firstOrFail();
$this->baseUrl = config('gusto.environment') === 'production'
? 'https://api.gusto.com'
: 'https://api.gusto-demo.com';
}
public function get(string $endpoint, array $query = []): array
{
return $this->request('GET', $endpoint, $query);
}
public function post(string $endpoint, array $data = []): array
{
return $this->request('POST', $endpoint, $data);
}
public function put(string $endpoint, array $data = []): array
{
return $this->request('PUT', $endpoint, $data);
}
private function request(string $method, string $endpoint, array $data): array
{
$this->refreshTokenIfNeeded();
$response = Http::withToken($this->settings->access_token)
->withHeaders([
'X-Gusto-API-Version' => '2026-02-01',
'Content-Type' => 'application/json',
])
->retry(3, 1000, fn($e) => $e->response?->status() === 429)
->{strtolower($method)}($this->baseUrl . $endpoint, $data);
if ($response->failed()) {
Log::error('Gusto API error', [
'endpoint' => $endpoint, 'status' => $response->status(),
'body' => $response->json(),
]);
$response->throw();
}
return $response->json() ?? [];
}
private function refreshTokenIfNeeded(): void
{
if (now()->lt($this->settings->token_expires_at->subSeconds(60))) {
return; // Token still valid
}
// Lock row to prevent race condition on concurrent refresh
DB::transaction(function() {
$settings = GustoSetting::lockForUpdate()->first();
$response = Http::post($this->baseUrl . '/oauth/token', [
'client_id' => config('gusto.client_id'),
'client_secret' => config('gusto.client_secret'),
'grant_type' => 'refresh_token',
'refresh_token' => $settings->refresh_token,
]);
$data = $response->json();
$settings->update([
'access_token' => $data['access_token'],
'refresh_token' => $data['refresh_token'],
'token_expires_at' => now()->addSeconds($data['expires_in']),
]);
$this->settings = $settings;
});
}
}
10 Database Migrations & Model Changes
10.1 Gusto Settings Table
Schema::create('gusto_settings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('company_id');
$table->string('gusto_company_uuid')->nullable();
$table->string('client_id')->nullable();
$table->string('client_secret')->nullable();
$table->enum('environment', ['sandbox', 'production'])->default('sandbox');
$table->text('access_token')->nullable();
$table->text('refresh_token')->nullable();
$table->timestamp('token_expires_at')->nullable();
$table->string('webhook_secret')->nullable();
$table->boolean('status')->default(false);
$table->timestamps();
});
10.2 Payroll Runs Table
Schema::create('payroll_runs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('company_id');
$table->unsignedBigInteger('pay_period_id');
$table->string('gusto_payroll_id')->nullable();
$table->enum('status', [
'draft', 'pending_review', 'pending_approval',
'approved', 'submitted', 'processing',
'calculated', 'gl_posted', 'completed', 'voided'
])->default('draft');
$table->integer('employee_count')->default(0);
$table->decimal('total_gross', 14, 2)->default(0);
$table->decimal('total_deductions', 14, 2)->default(0);
$table->decimal('total_employer_taxes', 14, 2)->default(0);
$table->decimal('total_net', 14, 2)->default(0);
$table->decimal('total_employer_cost', 14, 2)->default(0);
$table->unsignedBigInteger('approved_by')->nullable();
$table->timestamp('approved_at')->nullable();
$table->timestamp('processed_at')->nullable();
$table->json('exceptions')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
$table->softDeletes();
});
10.3 Paychecks Table
Schema::create('paychecks', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('payroll_run_id');
$table->unsignedBigInteger('employee_id');
$table->string('gusto_paycheck_id')->nullable();
$table->decimal('gross_pay', 12, 2)->default(0);
$table->decimal('net_pay', 12, 2)->default(0);
$table->decimal('total_taxes', 12, 2)->default(0);
$table->decimal('total_deductions', 12, 2)->default(0);
$table->decimal('total_employer_cost', 12, 2)->default(0);
$table->decimal('regular_hours', 8, 2)->default(0);
$table->decimal('overtime_hours', 8, 2)->default(0);
$table->decimal('pto_hours', 8, 2)->default(0);
$table->json('taxes_detail')->nullable(); // Full tax breakdown from Gusto
$table->json('benefits_detail')->nullable(); // Benefits deduction detail
$table->json('earnings_detail')->nullable(); // Earnings breakdown
$table->json('deductions_detail')->nullable(); // All deductions
$table->json('fund_allocations')->nullable(); // Fund split snapshot
$table->timestamps();
});
10.4 Employee Details Extension
Schema::table('employee_details', function (Blueprint $table) {
$table->decimal('annual_salary', 12, 2)->nullable();
$table->enum('pay_type', ['salary', 'hourly'])->default('salary');
$table->unsignedBigInteger('position_id')->nullable();
$table->string('gusto_employee_id')->nullable();
$table->string('gusto_sync_status', 20)->nullable(); // synced, pending, error
$table->timestamp('gusto_last_synced_at')->nullable();
});
See the full Markdown document docs/PAYROLL_GUSTO_INTEGRATION_PLAN.md for complete migration schemas for all 11 new tables (positions, salary_schedules, salary_schedule_steps, employee_salary_placements, pay_calendars, pay_periods, employee_fund_allocations, payroll_journal_entries).
11 Environment & Configuration
11.1 Environment Variables
# .env additions for Gusto integration
GUSTO_CLIENT_ID=your_gusto_client_id
GUSTO_CLIENT_SECRET=your_gusto_client_secret
GUSTO_ENVIRONMENT=sandbox # sandbox | production
GUSTO_COMPANY_UUID= # Set after company creation
GUSTO_WEBHOOK_SECRET= # Set after webhook verification
11.2 Config File
// config/gusto.php
return [
'client_id' => env('GUSTO_CLIENT_ID'),
'client_secret' => env('GUSTO_CLIENT_SECRET'),
'environment' => env('GUSTO_ENVIRONMENT', 'sandbox'),
'base_url' => env('GUSTO_ENVIRONMENT') === 'production'
? 'https://api.gusto.com'
: 'https://api.gusto-demo.com',
'api_version' => '2026-02-01',
'webhook_secret' => env('GUSTO_WEBHOOK_SECRET'),
'sync_interval' => 15, // minutes between reconciliation checks
'token_expiry' => 7200, // seconds (2 hours)
'refresh_buffer' => 60, // seconds before expiry to trigger refresh
];
11.3 Webhook Route
// routes/api.php
Route::post('gusto/webhooks', [GustoWebhookController::class, 'handle'])
->withoutMiddleware(['auth:sanctum'])
->name('gusto.webhooks');
11.4 Scheduled Jobs
// app/Console/Kernel.php
$schedule->job(new ReconcileGustoEmployees)->dailyAt('02:00');
$schedule->job(new SyncBenefitsFromGusto)->dailyAt('03:00');
12 Implementation Phases & Timeline
| Phase | Weeks | Deliverables | Gusto APIs Used |
|---|---|---|---|
| Phase 1: Foundation | 1-4 |
Position control models + CRUD Salary schedules (step/lane) Pay calendar & pay periods Employee fund allocation (multi-fund split) Seed CSD salary data |
None (internal models only) |
| Phase 2: Gusto Connect | 5-8 |
GustoClient + OAuth flow + settings UI Company, department, location sync Employee sync (Observer → Job → Gusto) Webhook endpoint + verification Sandbox testing |
Company setup (4.1) Locations (4.2) Departments (4.3) Employees (4.4, 4.5, 4.6) Jobs & Compensations (4.7) Webhooks (4.20) |
| Phase 3: Payroll + GL | 9-12 |
TimesheetAggregationService Payroll run workflow (8-step lifecycle) Payroll approval integration GL journal entry generation (fund splits) Post to QuickBooks/ERPNext Dashboard (replace demo) |
Pay Schedules (4.9) Earning Types (4.10) Payroll Operations (4.11) Pay Stubs (4.12) GL Report (4.19) |
| Phase 4: Benefits + Polish | 13-16 |
Benefits plan management Employee enrollment sync Garnishments integration 1099 contractor sync Pay stub viewer W-2 / year-end forms UAT & production cutover |
Benefits (4.13) Garnishments (4.14) Contractors (4.15) Time Off (4.16) Tax & Compliance (4.17) Forms (4.18) |
Total: 16 weeks (4 months) | Team: 1-2 backend + 1 frontend developer
13 Risks & Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Gusto async processing delays Calculate & submit return 202, results may take minutes |
High | Implement webhook + polling fallback. Show "processing" state in UI. Queue GL posting as separate job. |
| Token expiration race condition Multiple concurrent requests trigger simultaneous token refresh |
High | Lock database row during refresh (lockForUpdate()). Refresh 60s before expiry. Cache token in memory. |
| Employee data sync failures Validation errors when pushing employees to Gusto |
High | Store gusto_sync_status per employee. Daily reconciliation job. Alert dashboard for failed syncs. |
| Fund split rounding errors Splitting cents across multiple funds may not balance |
High | Assign remainder to primary fund. Validate total debits = total credits before posting. Add balancing entry if needed. |
| Payroll deadline pressure System downtime blocks payroll |
Critical | Gusto web portal is a manual fallback. Payroll data can be entered directly in Gusto if Missio is down. |
| PII / sensitive data SSN, bank accounts are high-risk data |
Critical | SSN and bank info never stored in Missio. Entered directly in Gusto. Only gusto_employee_id (UUID) stored. |
| Gusto API version deprecation Gusto may deprecate older API versions |
Medium | Pin version via X-Gusto-API-Version header. Monitor Gusto changelog. Abstraction layer in GustoClient. |
| Georgia TRS reporting May not be natively supported by Gusto |
High | Verify with Gusto during onboarding. If not supported, build export from paycheck data in Missio. |
14 Open Questions
| # | Question | Needed By | Owner |
|---|---|---|---|
| Q1 | Confirm Gusto as payroll provider (vs ADP). Need pricing quote for ~900 employees. | Before Phase 2 | PM / CSD |
| Q2 | CSD Chart of Accounts — complete list of fund codes, function codes, object codes. | Phase 1 | CSD Finance Director |
| Q3 | Current certified, classified, and admin salary schedules for FY 2025-2026. | Phase 1 | CSD HR Director |
| Q4 | Pay frequency per employee group — semi-monthly for certified? Biweekly for classified? | Phase 1 | CSD HR Director |
| Q5 | Does Gusto handle Georgia TRS reporting natively, or do we need a separate export? | Phase 4 | Gusto rep |
| Q6 | Should payroll GL entries post to ERPNext, QuickBooks, or both? | Phase 3 | PM / Finance |
| Q7 | Employee self-service: view pay stubs in Missio portal or redirect to Gusto portal? | Phase 4 | Product |
| Q8 | Are substitute teachers on the same payroll system, or a separate vendor? | Phase 2 | CSD HR Director |
| Q9 | Historical payroll data — does CSD need prior year data imported into Gusto? | Phase 2 | CSD Finance |
| Q10 | Gusto Embedded vs Gusto Pro — which tier do we need for API access? | Before Phase 2 | PM / Gusto rep |
15 Portal Pages & Features Summary
This section provides a consolidated view of all new portal pages required and their key features. Pages marked "replaces demo" replace existing static demo UIs with fully functional backends.
15.1 Portal Pages to Add
| # | Page | Route | Features | Notes |
|---|---|---|---|---|
| 1 | Payroll Dashboard | /account/payroll |
Current & recent payroll runs, totals (gross/net/taxes), exceptions list, pay period status indicators, payroll run history | Replaces demo |
| 2 | Payroll Run Detail | /account/payroll/{id} |
Per-employee breakdown, review & resolve exceptions, approve/reject workflow, submit to Gusto, view calculated results | New page |
| 3 | Pay Calendar Management | /account/pay-calendars |
CRUD for pay schedules (weekly / biweekly / semi-monthly / monthly), fiscal year configuration, auto-generate pay periods | New page |
| 4 | Position Control | /account/positions |
Position listing with status (authorized / filled / vacant / frozen / abolished), FTE tracking, fund allocation per position, budget vs actual comparison | Replaces demo |
| 5 | Salary Schedules | /account/salary-schedules |
Step/lane matrix CRUD (e.g., Step 5, Lane M+30 = $52,450), types: certified / classified / administrative / substitute, board approval workflow | New page |
| 6 | Employee Salary Placement | Employee profile tab | Assign employee to salary schedule + step + lane, stipends management (coaching, mentoring, additional duties), contract days, daily/hourly rate auto-calc | New tab on existing employee profile |
| 7 | Benefits Administration | /account/benefits |
Benefit plan management, employee enrollment tracking, employer cost summaries, Gusto sync status per plan | Replaces demo |
| 8 | Pay Stub Viewer | /account/paychecks |
Employee self-service pay stubs, itemized earnings / deductions / taxes, PDF download (pulled from Gusto API), pay history | New page |
| 9 | Employee Fund Allocation | /account/fund-allocations |
Multi-fund salary split per employee (e.g., 60% General Fund / 40% Title I), percentage validation (must sum to 100%), effective date tracking | New page |
| 10 | Payroll Journal Entries | /account/payroll-journal-entries |
GL entries generated from payroll results, debit/credit by fund + account code, QuickBooks posting status, budget allocation impact | New page |
| 11 | Gusto Settings | /account/gusto-settings |
OAuth2 connection management, API environment toggle (sandbox/production), sync status dashboard, webhook configuration | New page (similar to QB Settings) |
15.2 Key Features by Area
Payroll Processing
- Full payroll lifecycle: Draft → Review → Approve → Submit to Gusto → Receive Results → Post to GL → Completed
- Automatic timesheet aggregation — approved hours by employee by pay period
- Overtime calculation with 40hr/week FLSA threshold rules
- Payroll approval workflow integrated with existing
ApprovalController - Off-cycle payroll support (bonuses, corrections, termination pay)
- Exception handling & resolution before submission
Position Control
- Track authorized vs filled vs vacant vs frozen positions
- Link positions to fund codes, budget allocations, and salary schedules
- FTE tracking (1.0 = full-time, 0.5 = half-time, etc.)
- Grant-funded position flag with grant code tracking
- Location/school/building assignment
- Budget vs actual salary cost comparison per position
Salary & Compensation
- K-12 step/lane salary matrices (education level vs years of experience)
- Stipend management: coaching, mentoring, additional duties
- Substitute teacher pay rates and auto-calculation
- Pay type support: salary vs hourly
- Contract days configuration (typically 190 for teachers)
- Auto-calculate daily rate and hourly rate from annual salary
GL / Accounting Integration
- Auto-generate journal entries from Gusto payroll results
- Multi-fund salary splitting across fund codes (e.g., General Fund + Title I)
- Auto-post to QuickBooks Online (extends existing QB integration)
- Update
BudgetAllocation.expended_amountper fund on each payroll run - Account code mapping: Missio account codes → QuickBooks Account IDs
- Debit/credit balancing validation before GL posting
Employee Self-Service
- View current and historical pay stubs with full earnings/deduction detail
- Download pay stub PDFs
- View benefits enrollment information
- View salary placement (schedule, step, lane, stipends)
Employee Profile Additions
| New Field | Description |
|---|---|
annual_salary | Annual salary amount (existing hourly_rate remains for hourly employees) |
pay_type | Salary or hourly designation |
position_id | Link to Position Control record |
salary_schedule_id / salary_step / salary_lane | Salary schedule placement |
pay_calendar_id | Assigned pay calendar (semi-monthly, biweekly, etc.) |
gusto_employee_id | Gusto external reference ID |
gusto_sync_status | Sync status: synced / pending / error |
gusto_last_synced_at | Timestamp of last successful Gusto sync |
Gusto API Docs: https://docs.gusto.com/embedded-payroll/reference
Gusto Sandbox: https://api.gusto-demo.com
Current API Version: v2026-02-01
Document prepared for internal planning. Subject to revision based on provider selection and CSD requirements.
Last updated: April 6, 2026