ml-connector
XeroGusto

Xero and Gusto integration

Xero handles your accounting; Gusto handles your payroll. Connecting the two keeps your general ledger synchronized with your payroll cycle and your contact records in agreement. Employee changes in Gusto flow into Xero as contact updates so your records stay current. After each payroll run, labor costs from Gusto are recorded in Xero as manual journal entries without manual re-keying. ml-connector handles the different OAuth2 authorization flows and webhooks on each side, maps payroll dimensions to Xero tracking categories, and tracks every record in an audit trail.

How Xero works

Xero Accounting API exposes invoices, contacts, purchase orders, payments, general ledger accounts, manual journals, bank transactions, and tracking categories through REST endpoints at https://api.xero.com/api.xro/2.0/. All requests require the Xero-tenant-id header to target the correct organization. Xero authenticates with OAuth2 Authorization Code flow; access tokens expire in 30 minutes and refresh tokens in 60 days. Xero publishes webhooks for invoice, contact, payment, and manual journal events with metadata only, so records are fetched via follow-up GET calls. Xero also supports polling via If-Modified-Since header for delta sync. Rate limits are 5 concurrent calls and 60 per minute per tenant, with 5000 per day per tenant and 10000 per minute app-wide. Pagination is page-based with 100 records per page.

How Gusto works

Gusto exposes employees, payroll runs, compensations, contractors, benefits, and terminations through REST endpoints at https://api.gusto.com/v1/. Gusto authenticates with OAuth2 Authorization Code flow scoped to a single company per token; access tokens expire in 2 hours and refresh tokens never expire but are single-use and rotate on each refresh. Gusto publishes webhooks for payroll events (created, updated, calculated, submitted, processed, paid), employee events (created, updated, terminated), and company events. Webhook endpoints must return 2xx status within 10 seconds, and Gusto retries up to 16 times over 3 days. Webhook signatures are verified via HMAC-SHA256 using the X-Gusto-Signature header. Rate limit is 200 requests per minute per OAuth grant. Dollar amounts are returned as string decimals, not numeric types. Pagination supports both page-based and cursor-based styles. Gusto has no native GL accounts or invoicing entities.

What moves between them

The integration flows payroll from Gusto into Xero. Employees created or updated in Gusto are synced to Xero as contacts with job title and department information. After each payroll run, compensation totals from Gusto are captured and posted as manual journal entries in Xero to record labor costs against the correct general ledger accounts. Employee terminations in Gusto trigger contact status updates in Xero. Department and location data from Gusto map to Xero tracking categories so payroll allocations land on valid dimensions. Reference data is synchronized on a cadence tied to your payroll schedule. No financial data flows from Xero back to Gusto, since Gusto has no GL accounts or invoicing entities.

How ml-connector handles it

ml-connector maintains separate OAuth2 sessions for Xero and Gusto, refreshing tokens as needed when access tokens expire. For Xero, it stores the tenant ID per customer and includes it on every API call. For Gusto, it handles the single-company-per-token constraint by maintaining one OAuth grant per company. ml-connector listens for Gusto payroll completion webhooks and uses HMAC-SHA256 to verify each webhook signature against the subscription verification token. On webhook receipt, it fetches the payroll details, transforms compensation by department and job code, and constructs manual journal entries in Xero mapped to the correct GL accounts and tracking categories. Employee records from Gusto webhooks trigger contact lookups in Xero; new employees create contacts, and terminations flag contacts as inactive. ml-connector respects Xero rate limits by queueing writes, and it handles Gusto's string decimal amounts by parsing them before insertion. Every transaction carries a record ID for audit and replay if a downstream call fails.

A real-world example

A growing professional services firm uses Xero for accounting and Gusto for payroll across three offices. Before the integration, the finance team exported payroll registers from Gusto each pay period and manually entered labor totals into Xero by cost center and job code, a process that was error-prone and delayed month-end close by several days. New hires were added to Xero manually weeks after onboarding in Gusto. With Xero and Gusto connected, each payroll run automatically posts labor costs to the correct GL accounts segmented by office and department, and employee records sync in real time. The team now starts month-end with payroll already reconciled and has eliminated the manual entry step.

What you can do

  • Sync Gusto employees and contractors to Xero contacts with department and job title information.
  • Automatically post payroll labor costs from Gusto as manual journal entries in Xero after each pay run.
  • Map Gusto departments and locations to Xero tracking categories so payroll allocations land on valid accounts.
  • Verify Gusto webhook signatures with HMAC-SHA256 and handle Xero and Gusto OAuth2 token refresh automatically.
  • Track every employee change and payroll posting with a full audit trail for compliance and error recovery.

Questions

What payroll data flows from Gusto into Xero?
Employee records and compensation totals flow from Gusto to Xero. Employees created or updated in Gusto sync as contacts in Xero with their job title and department. After each payroll run, ml-connector reads compensation data from Gusto and creates manual journal entries in Xero to record labor costs against the correct GL accounts. No financial data flows back from Xero to Gusto, since Gusto has no GL accounts.
How does the integration handle the different OAuth2 flows and token expiry?
ml-connector maintains separate OAuth2 sessions for Xero and Gusto and refreshes access tokens as needed. Xero access tokens expire in 30 minutes, and ml-connector refreshes them with the 60-day refresh token before they expire. Gusto access tokens expire in 2 hours and are refreshed automatically; Gusto refresh tokens never expire but rotate on each refresh, so ml-connector stores the new token each time. Both integrations are stateless from Xero and Gusto's perspective.
What happens when a Gusto webhook fails or arrives late?
ml-connector verifies every Gusto webhook signature with HMAC-SHA256 before processing and logs a record of each payroll event. If a manual journal entry fails to post to Xero, the transaction is queued for retry with exponential backoff. Every record carries an idempotency key so retries do not create duplicates. The full audit trail lets you see which payroll cycles have been posted and replay any that failed after a fix.

Related integrations

Connect Xero and Gusto

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started