Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Feature ID: PF-65
Status: πŸ“ Planned
Created: 2026-02-05
Version: v1.0.0
Spec Reference: specs/pf/specs/PF-65-gusto-embedded-payroll-integration.md (v1.0)

Overview

PF-65 provides a Platform Integration Layer for the Gusto Embedded React SDK (@gusto/embedded-react-sdk). It enables HR (and other cores) to embed Gusto payroll and onboarding workflows inside Encore Health OS without implementing auth or proxy logic. All SDK traffic goes through a backend proxy (Supabase Edge Function) that adds OAuth2 tokens and x-gusto-client-ip.

Integration Patterns

TypePatternLocationStatus
Platform LayerPattern 1@/platform/gusto (or equivalent)πŸ“ Planned
Backend ProxyAPI (internal)Supabase Edge Function gusto-proxyπŸ“ Planned
PF-35Credential storagepf_integrations (integration_type gusto_embedded)πŸ“ Planned
HR consumerPlatform Layer consumerHR routes under /hr/payroll/gusto, /hr/me/onboardingπŸ“ Planned

Platform Integration Layer

Location: /src/platform/gusto/ (to be created) Public API (from spec):
// Re-exports from @gusto/embedded-react-sdk (configured).
// Note: Gusto docs use Employee.EmployeeList; verify package exports (top-level EmployeeList vs Employee.EmployeeList) at implementation.
export { GustoProvider, EmployeeOnboardingFlow, EmployeeList, componentEvents } from '@gusto/embedded-react-sdk';

export function useGustoConfig(): {
  baseUrl: string;
  companyId: string | null;
  isConnected: boolean;
  isLoading: boolean;
  error: Error | null;
};

export function useGustoConnectionStatus(organizationId: string): {
  status: 'connected' | 'disconnected' | 'error';
  gustoCompanyId: string | null;
  isLoading: boolean;
};
Usage (HR consumer):
import { GustoProvider, EmployeeOnboardingFlow, useGustoConfig } from '@/platform/gusto';

function HRGustoOnboardingPage() {
  const { baseUrl, companyId, isConnected, isLoading, error } = useGustoConfig();
  if (!isConnected || !companyId) return <EmptyState />;
  return (
    <GustoProvider config={{ baseUrl }}>
      <EmployeeOnboardingFlow companyId={companyId} onEvent={handleEvent} />
    </GustoProvider>
  );
}
Permissions: hr.gusto.view (embed components), hr.gusto.configure (configure connection and company mapping).

API Contract: Gusto Proxy

Endpoint: Supabase Edge Function, e.g. POST /functions/v1/gusto-proxy Behavior:
  • Receives request from SDK (path + body match Gusto Embedded API 1:1).
  • Resolves authenticated user’s organization_id; loads that org’s Gusto OAuth access token from PF-35 (pf_integrations + credentials).
  • Adds Authorization: Bearer <token> and x-gusto-client-ip (from X-Forwarded-For or X-Real-IP).
  • Forwards to https://api.gusto.com (exact path per Gusto Embedded API docs).
  • On 401: attempts token refresh (using stored refresh token). If the authorization server returns a new refresh token in the refresh response, the proxy must persist and replace the old refresh token in the credential store (PF-35 / pf_integrations) so future refreshes use the new value. The retry MUST use the newly issued access token in the Authorization: Bearer <token> header. On refresh or persist failure, return a sanitized error (e.g. 502 β€œReconnect to Gusto”) without exposing tokens.
Request (from client): Transparent pass-through; client sends same path/body as to Gusto API. Proxy does not alter body. Response: Same as Gusto API response; errors sanitized (no tokens or stack traces). Error codes: Document in API_CONTRACTS.md (e.g. 401 Unauthorized, 502 Bad Gateway, 503 Service Unavailable).

Security

  • OAuth tokens and refresh tokens only server-side (PF-35 / Vault); never in frontend.
  • x-gusto-client-ip (trusted client IP): The Edge Function (or middleware) must derive a trusted client IP as follows:
    • Prefer X-Forwarded-For: use the rightmost entry added by your trusted edge proxy (i.e., the last IP in the chain), since the leftmost is typically the original client and the rightmost is the one added by the proxy closest to this service.
    • Fall back to X-Real-IP only if the request is known to have passed through a trusted proxy that sets it.
    • Otherwise use the connection remote address.
    • The Edge Function must validate/sanitize the chosen IP: parse and canonicalize it, reject private/reserved ranges unless explicitly expected (e.g., internal networks), and verify the request passed through a known proxy list when using forwarded headers. If no trusted IP can be determined, set the header value to β€œunknown” and log a warning.
  • Proxy and company resolution scoped by organization_id; no cross-tenant data.

SDK Event Types

The Gusto Embedded SDK emits events via onEvent callback. Encore Health OS handles:
Event NameDescriptionEncore Health OS Action
employee/createdNew employee added to GustoToast notification, analytics
onboarding/completeEmployee finished onboardingToast notification, analytics
Full Event Reference: See Gusto Embedded SDK Documentation

References

  • Spec: specs/pf/specs/PF-65-gusto-embedded-payroll-integration.md
  • PF-35: Integration Hub – specs/pf/specs/PF-35-integration-hub.md
  • API Contracts: docs/architecture/integrations/API_CONTRACTS.md (proxy to be added)
  • Platform Integration Layers: docs/architecture/integrations/PLATFORM_INTEGRATION_LAYERS.md
  • Gusto Embedded API: https://docs.gusto.com/embedded-payroll/docs/