Missio ERP + Gusto Payroll Integration

Technical Integration Specification & Implementation Plan
Project City Schools of Decatur - K-12 ERP
RFP 26-001
Branch csdecatur
Date April 6, 2026
Status Planning / Architecture

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
↓ Employee sync       ↑ Payroll results       ↓ GL journal entries
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 qualityModern REST, well-documented, embedded SDKSOAP/REST hybrid, complex onboarding
K-12 supportMulti-EIN, multi-location, multi-pay-scheduleBetter for very large enterprise
Integration effort4-6 weeks8-12 weeks
Cost per employee~$6-12/mo~$15-25/mo
Benefits adminBuilt-inSeparate module
Tax filingAll 50 states + federalAll 50 states + federal
Embedded React SDKFull payroll UI componentsNot available
API versionv2026-02-01 (latest)Varies

2 Integration Architecture

System Ownership Model

Data / ProcessOwnerDirectionSync Trigger
Employee profile (name, email, DOB, address)MissioMissio → GustoReal-time (Observer)
SSN / bank account / direct depositGustoEntered directly in GustoEmployee onboarding
Job title, department, work locationMissioMissio → GustoReal-time (Observer)
Compensation rate (salary/hourly)MissioMissio → GustoOn salary placement change
Time & attendance hoursMissioMissio → GustoPer payroll run
Leave / PTO balancesMissioMissio → GustoPer payroll run
Tax calculation (fed/state/FICA/Medicare)GustoGusto → MissioAfter payroll calculate
Deductions (benefits, garnishments)GustoGusto → MissioAfter payroll calculate
Net pay & direct depositGustoGusto → MissioAfter payroll submit
Pay stubs / W-2 formsGustoGusto → Missio (display)After payroll processed
GL journal entriesMissioMissio → ERPNext/QBAfter payroll results received
Benefits enrollmentGustoGusto → MissioOpen enrollment / life event
W-4 / state tax electionsGustoGusto managedEmployee self-service
Key Principle: Sensitive data never stored in Missio SSNs, bank account numbers, and tax form data are entered and stored exclusively in Gusto. Missio only stores the gusto_employee_id reference UUID. This eliminates PCI/PII liability from the Missio database.

3 Gusto API Authentication

3.1 Base URLs

EnvironmentBase URLPurpose
Sandboxhttps://api.gusto-demo.comDevelopment & testing
Productionhttps://api.gusto.comLive 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 TypeScopeExpirationUse 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

POST /oauth/token
{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "grant_type": "system_access"
}
Response
{
  "access_token": "system_level_token_here",
  "token_type": "Bearer",
  "created_at": 1712345678,
  "expires_in": 7200
}

3.5 Company Access Token Request (Refresh)

POST /oauth/token
{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "grant_type": "refresh_token",
  "refresh_token": "previously_stored_refresh_token"
}
Response
{
  "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
}
Token Refresh Best Practices Store 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

ScopeAccessUsed For
employees:readReadFetching employee list and details
employees:writeWriteCreating/updating employees
compensations:readReadReading salary/rate data (sensitive)
compensations:writeWriteSetting compensation rates
payrolls:readReadViewing payroll data and results
payrolls:runWriteCreating, calculating, submitting payroll
employee_benefits:readReadBenefits enrollment data
employee_benefits:writeWriteManaging benefits enrollment
employee_benefits:read:phiReadBenefits with PHI visibility
company_benefits:readReadCompany benefit plan configuration
company_benefits:writeWriteCreating/modifying benefit plans
pay_schedules:readReadPay schedule and period data
pay_schedules:writeWriteCreating/modifying pay schedules
webhook_subscriptions:writeWriteManaging webhook subscriptions
contractors:readRead1099 contractor data
contractors:writeWriteManaging contractors
contractor_payments:readReadContractor 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

MethodEndpointPurposeWhen Called
POST/v1/partner-managed-companiesCreate partner-managed company for CSDOne-time setup
GET/v1/companies/{company_id}Get company details, onboarding statusSetup verification
PUT/v1/companies/{company_id}Update company info (EIN, address)As needed
GET/v1/companies/{company_id}/onboarding_statusCheck company setup progressDuring onboarding
PUT/v1/companies/{company_id}/finish_onboardingMark company onboarding completeOne-time
GET/v1/companies/{company_id}/custom_fieldsGet custom fields for employee dataSetup

4.2 Locations

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/locationsCreate school/building locationsSetup per school
GET/v1/companies/{company_id}/locationsList all locationsSync
PUT/v1/locations/{location_id}Update location detailsAs needed
GET/v1/locations/{location_id}/minimum_wagesGet minimum wage for locationCompliance check

4.3 Departments

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/departmentsCreate department (mirrors Missio Team)Setup & sync
GET/v1/companies/{company_id}/departmentsList all departmentsSync verification
PUT/v1/departments/{department_id}Update department nameOn Team change
DELETE/v1/departments/{department_id}Remove departmentOn Team delete

4.4 Employees (W-2)

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/employeesCreate new employeeEmployeeObserver::created()
GET/v1/companies/{company_id}/employeesList all employees (active, terminated, onboarding)Bulk sync / reconciliation
GET/v1/employees/{employee_id}Get single employee detailsSync verification
PUT/v1/employees/{employee_id}Update employee infoEmployeeObserver::updated()
DELETE/v1/employees/{employee_id}Remove onboarding employeeIf hire cancelled

4.5 Employee Onboarding & Compliance

MethodEndpointPurposeWhen Called
GET/v1/employees/{employee_id}/onboarding_statusCheck employee setup progressDashboard display
PUT/v1/employees/{employee_id}/onboarding_statusUpdate onboarding stepDuring onboarding
GET/v1/employees/{employee_id}/federal_taxesGet W-4 dataDisplay/sync
PUT/v1/employees/{employee_id}/federal_taxesUpdate W-4 withholdingEmployee self-service
GET/v1/employees/{employee_id}/state_taxesGet GA state tax electionsDisplay/sync
PUT/v1/employees/{employee_id}/state_taxesUpdate GA state withholdingEmployee self-service
PUT/v1/employees/{employee_id}/i9_authorizationUpdate I-9 work authorizationDuring onboarding
GET/v1/employees/{employee_id}/employment_historyGet employment historyReporting

4.6 Employee Addresses

MethodEndpointPurposeWhen Called
POST/v1/employees/{employee_id}/home_addressesAdd home addressEmployee create/update
PUT/v1/home_addresses/{address_id}Update home addressAddress change
POST/v1/employees/{employee_id}/work_addressesAssign work location (school)Employee assignment
PUT/v1/work_addresses/{address_id}Update work locationTransfer

4.7 Jobs & Compensations

MethodEndpointPurposeWhen Called
POST/v1/employees/{employee_id}/jobsCreate job assignment (title, dept)On hire / transfer
GET/v1/employees/{employee_id}/jobsList employee jobsSync
PUT/v1/jobs/{job_id}Update job title/deptOn change
POST/v1/jobs/{job_id}/compensationsSet compensation (rate, payment_unit)Salary placement
GET/v1/jobs/{job_id}/compensationsGet compensation historyDisplay
PUT/v1/compensations/{compensation_id}Update rate (step/lane change)Annual increment
Compensation payment_unit Values 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

MethodEndpointPurposeWhen Called
POST/v1/employees/{employee_id}/terminationsTerminate employeeOn separation
GET/v1/employees/{employee_id}/terminationsGet termination detailsFinal pay processing
DELETE/v1/employees/{employee_id}/terminationsCancel terminationIf reversed
POST/v1/employees/{employee_id}/rehireRehire former employeeRehire

4.9 Pay Schedules & Pay Periods

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/pay_schedulesCreate pay schedule (semi-monthly, biweekly)One-time setup
GET/v1/companies/{company_id}/pay_schedulesList pay schedulesDashboard
PUT/v1/pay_schedules/{pay_schedule_id}Update scheduleConfiguration
GET/v1/companies/{company_id}/pay_schedules/previewPreview future pay datesCalendar setup
POST/v1/companies/{company_id}/pay_schedules/assignmentsAssign employees to scheduleEmployee setup
GET/v1/companies/{company_id}/pay_schedules/assignmentsGet schedule assignmentsSync
GET/v1/companies/{company_id}/pay_periodsList pay periods with datesPayroll dashboard
GET/v1/companies/{company_id}/unprocessed_termination_pay_periodsGet final pay periods for termed employeesFinal pay processing

4.10 Earning Types

MethodEndpointPurposeWhen Called
GET/v1/companies/{company_id}/earning_typesList all earning types (default + custom)Setup & mapping
POST/v1/companies/{company_id}/earning_typesCreate custom earning type (stipend, coaching)K-12 setup
PUT/v1/companies/{company_id}/earning_types/{earning_type_id}Update earning typeConfiguration
K-12 Custom Earning Types to Create Coaching Stipend, Mentoring Stipend, Curriculum Development, Extended Day Pay, Summer School Pay, National Board Certification, Longevity Pay, Department Chair Stipend, Extra Duty Pay.

4.11 Payroll Operations (Core)

MethodEndpointPurposeWhen Called
GET/v1/companies/{company_id}/payrollsList payrolls (filter by status, date range, type)Dashboard
GET/v1/companies/{company_id}/payrolls/{payroll_id}Get single payroll with employee compensationsAfter calculate/process
POST/v1/companies/{company_id}/payrollsCreate off-cycle payroll (bonus, correction)Off-cycle runs
PUT/v1/companies/{company_id}/payrolls/{payroll_id}Update payroll with employee hours/earningsBefore calculation
PUT/v1/companies/{company_id}/payrolls/{payroll_id}/calculateCalculate taxes, deductions, net payPreview step (async, returns 202)
PUT/v1/companies/{company_id}/payrolls/{payroll_id}/submitSubmit payroll for processing & paymentFinal submission (async, returns 202)
PUT/v1/companies/{company_id}/payrolls/{payroll_id}/cancelCancel unprocessed payrollIf voided
PUT/v1/companies/{company_id}/payrolls/{payroll_id}/preparePrepare payroll for editingBefore update
GET/v1/companies/{company_id}/payrolls/{payroll_id}/payroll-blockersGet issues preventing submissionPre-submit validation
GET/v1/companies/{company_id}/payroll_reversalsList payroll reversalsAudit
POST/v1/companies/{company_id}/payrolls/{payroll_id}/gross-upCalculate gross-up (bonus after tax)Bonus payroll
POST/v1/companies/{company_id}/payrolls/skipSkip a pay periodHoliday / school break

4.12 Pay Stubs & Receipts

MethodEndpointPurposeWhen Called
GET/v1/companies/{company_id}/employees/{employee_id}/pay_stubsList pay stubs for employeeEmployee self-service
GET/v1/companies/{company_id}/payrolls/{payroll_id}/employees/{employee_id}/pay_stubGet single pay stub (PDF)View/print
GET/v1/companies/{company_id}/payment_receipts/payrolls/{payroll_id}Get payroll payment receiptFinance records
POST/v1/companies/{company_id}/payrolls/{payroll_id}/generated_documents/printable_payroll_checksGenerate printable checksPaper check processing

4.13 Benefits Administration

MethodEndpointPurposeWhen Called
GET/v1/benefitsList all supported benefit typesSetup
POST/v1/companies/{company_id}/company_benefitsCreate company benefit planPlan setup
GET/v1/companies/{company_id}/company_benefitsList company benefit plansDashboard
PUT/v1/company_benefits/{benefit_id}Update benefit planRate changes
DELETE/v1/company_benefits/{benefit_id}Deactivate benefit planPlan termination
POST/v1/employees/{employee_id}/employee_benefitsEnroll employee in benefitOpen enrollment / new hire
GET/v1/employees/{employee_id}/employee_benefitsList employee enrollmentsDisplay
PUT/v1/employee_benefits/{employee_benefit_id}Update enrollment (coverage level)Life event / open enrollment
DELETE/v1/employee_benefits/{employee_benefit_id}Remove enrollmentDisenrollment

4.14 Garnishments

MethodEndpointPurposeWhen Called
POST/v1/employees/{employee_id}/garnishmentsCreate garnishment (child support, tax levy)Court order
GET/v1/employees/{employee_id}/garnishmentsList employee garnishmentsDisplay
PUT/v1/garnishments/{garnishment_id}Update garnishmentAmount change
GET/v1/employees/{employee_id}/garnishments/child_supportGet child support detailsCompliance

4.15 1099 Contractors

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/contractorsCreate contractorVendor onboarding
GET/v1/companies/{company_id}/contractorsList contractorsSync with Missio vendors
PUT/v1/contractors/{contractor_id}Update contractorProfile change
POST/v1/companies/{company_id}/contractor_paymentsCreate contractor paymentInvoice payment
GET/v1/companies/{company_id}/contractor_paymentsList contractor payments1099 reporting

4.16 Time Off Policies

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/time_off_policiesCreate PTO/sick policySetup
GET/v1/companies/{company_id}/time_off_policiesList time off policiesSync
PUT/v1/time_off_policies/{policy_id}/balanceAdjust employee PTO balanceMissio leave sync
PUT/v1/time_off_policies/{policy_id}/add_employeesEnroll employees in policyNew hire
PUT/v1/time_off_policies/{policy_id}/remove_employeesRemove employee from policyTermination
GET/v1/employees/{employee_id}/time_off_activitiesGet PTO history & accrualsDashboard

4.17 Tax & Compliance

MethodEndpointPurposeWhen Called
GET/v1/companies/{company_id}/federal_tax_detailsGet company federal tax setupCompliance audit
PUT/v1/companies/{company_id}/federal_tax_detailsUpdate federal tax detailsConfiguration
GET/v1/companies/{company_id}/tax_requirementsGet state tax requirements (GA)Compliance
PUT/v1/companies/{company_id}/tax_requirementsFulfill tax requirementsState registration
GET/v1/companies/{company_id}/tax_liabilitiesGet tax liabilitiesReporting

4.18 Forms & Documents

MethodEndpointPurposeWhen Called
GET/v1/companies/{company_id}/formsList company forms (941, W-3)Year-end
GET/v1/employees/{employee_id}/formsList employee forms (W-2)Year-end self-service
GET/v1/generated_documents/{document_type}/{document_id}Download generated documentForm retrieval
POST/v1/sandbox/companies/{company_id}/generate_w2Generate test W-2 (sandbox only)Testing

4.19 Reporting & General Ledger

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/reportsGenerate custom reportReporting
GET/v1/companies/{company_id}/reports/{report_id}Get generated report dataReport retrieval
POST/v1/companies/{company_id}/general_ledgerGenerate GL report with debits/creditsAfter each payroll for GL posting
GET/v1/companies/{company_id}/report_templates/{report_type}Get report template structureSetup
POST/v1/companies/{company_id}/reports/employees_annual_fica_wageFICA wage reportYear-end
Key: General Ledger Endpoint The 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

MethodEndpointPurposeWhen Called
POST/v1/webhook_subscriptionsCreate webhook subscriptionOne-time setup
GET/v1/webhook_subscriptionsList webhook subscriptionsAdmin
PUT/v1/webhook_subscriptions/{subscription_id}Update subscription URL/eventsConfiguration
DELETE/v1/webhook_subscriptions/{subscription_id}Remove subscriptionCleanup
PUT/v1/webhook_subscriptions/{subscription_id}/verifyVerify webhook endpointDuring setup
GET/v1/webhook_subscriptions/health_checkCheck webhook healthMonitoring

4.21 Bank Accounts & Payment Methods

MethodEndpointPurposeWhen Called
POST/v1/companies/{company_id}/bank_accountsAdd company bank account for payroll fundingSetup
PUT/v1/companies/{company_id}/bank_accounts/{bank_id}/verifyVerify bank account (micro-deposits)Setup
POST/v1/employees/{employee_id}/bank_accountsAdd employee bank account for direct depositEmployee self-service
GET/v1/employees/{employee_id}/payment_methodGet employee payment methodDisplay
PUT/v1/employees/{employee_id}/payment_methodSet payment method (direct deposit/check)Employee setup
GET/v1/companies/{company_id}/payment_configsGet payment configurationSetup

5 Data Field Mapping (Missio → Gusto)

5.1 Employee Create/Update Mapping

Missio FieldMissio SourceGusto FieldNotes
user.name (split)User.namefirst_name, last_nameParse first/last from full name
user.emailUser.emailemailPrimary email
details.date_of_birthEmployeeDetails.date_of_birthdate_of_birthFormat: YYYY-MM-DD
details.addressEmployeeDetails.addresshome_address.street_1Parse street, city, state, zip
details.joining_dateEmployeeDetails.joining_datestart_dateHire date
user.mobileUser.mobilephoneOptional
SSNNot in MissiossnEntered directly in Gusto during onboarding

5.2 Job & Compensation Mapping

Missio FieldMissio SourceGusto FieldNotes
Designation nameDesignation.namejob.titleJob title text
Department nameTeam.team_namedepartment (UUID)Must create dept in Gusto first, map by name
School/buildingCompanyAddresswork_address (location UUID)Must create location in Gusto first
Salary (annual)EmployeeSalaryPlacement.total_salarycompensation.rateString decimal (e.g., "52450.00")
Pay typeEmployeeSalaryPlacement.pay_typecompensation.payment_unit"Year" for salary, "Hour" for hourly
Hourly rateEmployeeDetails.hourly_ratecompensation.rateFor hourly employees only
FLSA statusDerived from employment_typecompensation.flsa_status"Exempt" for salaried certified, "Nonexempt" for hourly

5.3 Time & Attendance → Payroll Mapping

Missio DataMissio SourceGusto Payroll FieldAggregation
Regular hoursProjectTimeLog (approved=true)hourly_compensations[].hours (Regular)Sum by employee for pay period, cap at 40hr/week
Overtime hoursProjectTimeLog (approved=true)hourly_compensations[].hours (Overtime)Hours exceeding 40hr/week threshold
PTO hours usedLeave (status=approved, paid=true)paid_time_off[].hours (Vacation)Sum approved paid leave in pay period
Sick hours usedLeave (type=sick, approved)paid_time_off[].hours (Sick)Sum approved sick leave in pay period
StipendsEmployeeSalaryPlacement.stipendsfixed_compensations[].amountPer-paycheck prorated amount

5.4 Department & Location Mapping

Missio EntityMissio ModelGusto EntityMapping Key
DepartmentTeamDepartmentStore gusto_department_id on Team or mapping table
School/BuildingCompanyAddressLocationStore gusto_location_id on CompanyAddress
Pay schedulePayCalendar (new)PayScheduleStore gusto_pay_schedule_id on PayCalendar

6 Payroll Run Lifecycle — Step-by-Step API Flow

1. Get Unprocessed Payrolls
2. Aggregate Hours
3. Update Payroll
4. Calculate
5. Review
6. Approve
7. Submit
8. Post GL

Blue = Missio    Orange = Gusto API (async)

Step 1: Get Unprocessed Payrolls from Gusto

Request
GET /v1/companies/{company_id}/payrolls?processing_statuses=unprocessed
Authorization: Bearer {company_access_token}
X-Gusto-API-Version: 2026-02-01
Response (simplified)
[
  {
    "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

Request
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)

Request
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 results after calculation
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)

Request
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"
Async Processing Both /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

  1. Create an HTTPS POST endpoint in Missio: /api/gusto/webhooks
  2. Create webhook subscription via API with desired event types
  3. Gusto sends verification_token POST to our endpoint
  4. Call PUT /webhook_subscriptions/{id}/verify with the token
  5. Implement HMAC-SHA256 signature verification using verification_token as secret
  6. Compare computed hash against X-Gusto-Signature request 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 TypeTriggered WhenMissio Action
payroll.paidPayroll processed, ACH initiatedFetch payroll results → store paychecks → generate GL entries → post to ERPNext/QB
employee.createdEmployee created in GustoUpdate gusto_employee_id in Missio (confirmation)
employee.updatedEmployee profile changed in GustoSync changes back to Missio if needed
employee.job_compensation.updatedCompensation changedLog for audit trail
employee.benefit.updatedBenefits enrollment changedUpdate benefits display in Missio
employee.terminatedEmployee terminatedUpdate employee status in Missio
company.bank_account.verifiedBank account verifiedUpdate setup status
form.availableW-2 or 941 readyNotify employee / show in portal
contractor_payment.processedContractor payment sentUpdate 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

Gusto GL Report
Parse by Employee
Apply Fund Splits
Generate JE
Update BudgetAllocation
Post to ERPNext/QB

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 TypeFund CodeAccount CodeDebitCredit
Salary Expense100 (General)6110 (Cert Salaries)$2,700.00
Salary Expense200 (Title I)6110 (Cert Salaries)$1,800.00
Federal Tax Payable1002110$324.00
Federal Tax Payable2002110$216.00
GA State Tax Payable1002120$135.00
GA State Tax Payable2002120$90.00
FICA (Employee)1002130$167.40
FICA (Employee)2002130$111.60
Medicare (Employee)1002140$39.15
Medicare (Employee)2002140$26.10
Net Pay (Cash)1001010$1,231.47
Net Pay (Cash)2001010$820.98
Employer FICA1006210$167.40
Employer FICA2006210$111.60
Employer Medicare1006220$39.15
Employer Medicare2006220$26.10

Yellow-highlighted rows = employer cost entries. Each entry updates the corresponding BudgetAllocation.expended_amount.

8.4 Account Code Mapping

Missio account_codeDescriptionTypeGusto GL Category
6110Certified SalariesExpense (Debit)Wages & Salaries
6120Classified SalariesExpense (Debit)Wages & Salaries
6210Employer FICA/MedicareExpense (Debit)Employer Taxes
6220Employer BenefitsExpense (Debit)Benefits
6230Employer Retirement (TRS)Expense (Debit)Benefits
2110Federal Tax PayableLiability (Credit)Employee Taxes
2120State Tax PayableLiability (Credit)Employee Taxes
2130FICA PayableLiability (Credit)Employee + Employer Taxes
2140Medicare PayableLiability (Credit)Employee + Employer Taxes
2150Benefits PayableLiability (Credit)Benefits
1010Cash – PayrollAsset (Credit)Net Pay

9 New Laravel Code — Services, Jobs, Controllers

9.1 New Directory Structure

app/Services/Gusto/ GustoClient.php # HTTP client (OAuth, token refresh, retry, logging) GustoEmployeeService.php # Employee CRUD sync GustoPayrollService.php # Payroll submission, calculate, submit, results GustoBenefitsService.php # Benefits plan & enrollment sync GustoCompanyService.php # Company, location, department sync GustoContractorService.php # 1099 contractor & payment sync GustoWebhookHandler.php # Incoming webhook processor GustoGLService.php # General Ledger report retrieval app/Services/ PayrollOrchestrationService.php # Main payroll workflow orchestrator PayrollJournalEntryService.php # GL entry generation with fund splits TimesheetAggregationService.php # Aggregate approved hours by pay period app/Http/Controllers/ PayrollController.php # Payroll runs dashboard (replaces demo) PayCalendarController.php # Pay calendar CRUD PositionController.php # Position control (replaces demo) SalaryScheduleController.php # Step/lane salary grid BenefitPlanController.php # Benefits admin (replaces demo) PaycheckController.php # Pay stub viewer (proxies Gusto) EmployeeFundAllocationController.php # Fund split per employee PayrollJournalEntryController.php # GL posting review GustoSettingsController.php # Gusto OAuth config UI app/Http/Controllers/Api/ GustoWebhookController.php # Webhook endpoint app/Jobs/ SyncEmployeeToGusto.php # Push employee to Gusto SyncEmployeeFromGusto.php # Pull employee updates from Gusto SyncDepartmentsToGusto.php # Sync Teams → Gusto Departments SyncLocationsToGusto.php # Sync CompanyAddresses → Gusto Locations SubmitPayrollToGusto.php # Send approved payroll to Gusto FetchPayrollResults.php # Pull calculated payroll from Gusto GeneratePayrollJournalEntries.php # Create GL entries from payroll PostPayrollToQuickBooks.php # Push JE to QB (extend existing) AggregateTimesheetHours.php # Sum approved hours per employee SyncBenefitsFromGusto.php # Pull benefit enrollment data ReconcileGustoEmployees.php # Daily reconciliation check app/Models/ Position.php # Position control SalarySchedule.php # Salary schedule SalaryScheduleStep.php # Step/lane matrix EmployeeSalaryPlacement.php # Employee ↔ schedule link PayCalendar.php # Pay frequency definition PayPeriod.php # Pay period dates & status PayrollRun.php # Payroll run lifecycle Paycheck.php # Individual paycheck record EmployeeFundAllocation.php # Multi-fund salary split PayrollJournalEntry.php # GL debit/credit entries GustoSetting.php # OAuth tokens & config app/Observers/ PayrollRunObserver.php # Status change events PositionObserver.php # Budget validation config/ gusto.php # Gusto API configuration routes/ payroll.php # All payroll routes database/migrations/ create_positions_table.php create_salary_schedules_table.php create_salary_schedule_steps_table.php create_employee_salary_placements_table.php create_pay_calendars_table.php create_pay_periods_table.php create_payroll_runs_table.php create_paychecks_table.php create_employee_fund_allocations_table.php create_payroll_journal_entries_table.php create_gusto_settings_table.php add_payroll_fields_to_employee_details.php add_gusto_ids_to_teams_table.php add_gusto_ids_to_company_addresses.php

9.2 Existing Files to Modify

FileChangeType
app/Models/EmployeeDetails.phpAdd relationships: position(), salaryPlacement(), fundAllocations(), payCalendar(). Add gusto_employee_id, gusto_sync_status fields.Modify
app/Models/Team.phpAdd gusto_department_id fieldModify
app/Models/CompanyAddress.phpAdd gusto_location_id fieldModify
app/Observers/EmployeeDetailObserver.phpDispatch SyncEmployeeToGusto job on create/updateModify
app/Http/Controllers/ApprovalController.phpAdd PayrollRun as approvable entity typeExtend
app/Http/Controllers/QuickbookController.phpAdd createPayrollJournalEntry() methodExtend
routes/budget.phpReplace demo routes with real controller routesModify
resources/views/sections/menu.blade.phpAdd Payroll sub-menu itemsModify
resources/views/employees/show.blade.phpAdd "Compensation" tabModify
app/Console/Kernel.phpSchedule Gusto sync & reconciliation jobsModify
routes/api.phpAdd Gusto webhook routeModify

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

PhaseWeeksDeliverablesGusto 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

RiskImpactMitigation
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

#QuestionNeeded ByOwner
Q1Confirm Gusto as payroll provider (vs ADP). Need pricing quote for ~900 employees.Before Phase 2PM / CSD
Q2CSD Chart of Accounts — complete list of fund codes, function codes, object codes.Phase 1CSD Finance Director
Q3Current certified, classified, and admin salary schedules for FY 2025-2026.Phase 1CSD HR Director
Q4Pay frequency per employee group — semi-monthly for certified? Biweekly for classified?Phase 1CSD HR Director
Q5Does Gusto handle Georgia TRS reporting natively, or do we need a separate export?Phase 4Gusto rep
Q6Should payroll GL entries post to ERPNext, QuickBooks, or both?Phase 3PM / Finance
Q7Employee self-service: view pay stubs in Missio portal or redirect to Gusto portal?Phase 4Product
Q8Are substitute teachers on the same payroll system, or a separate vendor?Phase 2CSD HR Director
Q9Historical payroll data — does CSD need prior year data imported into Gusto?Phase 2CSD Finance
Q10Gusto Embedded vs Gusto Pro — which tier do we need for API access?Before Phase 2PM / 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

#PageRouteFeaturesNotes
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_amount per 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 FieldDescription
annual_salaryAnnual salary amount (existing hourly_rate remains for hourly employees)
pay_typeSalary or hourly designation
position_idLink to Position Control record
salary_schedule_id / salary_step / salary_laneSalary schedule placement
pay_calendar_idAssigned pay calendar (semi-monthly, biweekly, etc.)
gusto_employee_idGusto external reference ID
gusto_sync_statusSync status: synced / pending / error
gusto_last_synced_atTimestamp 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