Track and engage employers through activation

Build automated employer funnel tracking using webhooks and GraphQL

Track and Engage Employers Through Activation

Track every step of your employers' activation journey with real-time webhooks and powerful GraphQL queries. This guide shows you how to build automated funnel tracking, proactive alerts, and lifecycle communications.

👍

Why integrate this?

  • Observability — See exactly where each employer is in their activation journey
  • Proactive support — Detect stuck employers before they churn
  • Automated engagement — Trigger emails and tasks without manual intervention
  • Data ownership — Your CRM has complete employer lifecycle data

Overview: The Employer Activation Journey

Employers go through two parallel tracks before they can run payroll:

  1. PayWorkersReadiness — Operational readiness (can this employer pay workers?)
  2. Account Standing — Risk and compliance readiness (is this employer approved to operate?)

Both must be satisfied for an employer to successfully run payroll.

PayWorkersReadiness Phases

StatusDescriptionWhat happens next
ONBOARDINGEmployer hasn't completed initial setupComplete hosted employer onboarding
IMPLEMENTATIONEmployer has prior payroll history to importImport payroll data from prior provider
READYEmployer is ready to run payrollNo action needed
PARTIALLY_READYMinor issues exist but payroll can runAddress outstanding requirements
📘

Typical transitions

Most employers follow one of two paths:

  • ONBOARDINGREADY (no prior payroll history)
  • ONBOARDINGIMPLEMENTATIONREADY (with prior payroll history)

Account Standing Phases

StatusDescriptionTypical duration
NEW_ACCOUNTEmployer needs to provide information for screeningUntil info submitted
IN_REVIEWPending Salsa action on provided information1-3 business days
ACTIVEEmployer and workers are compliantOngoing
RESTRICTEDMore information required to pass screeningUntil info provided
REJECTEDEmployer or workers failed screeningTerminal state
CONDITIONALApproved with conditions/monitoringOngoing with review
CANCELLEDEmployer account cancelledTerminal state
🚧

Important

Employers in RESTRICTED or IN_REVIEW status cannot run payroll until their account standing is resolved.

Requirements at a Glance

Both PayWorkersReadiness and Account Standing track specific requirements that must be met. Use the Capabilities API to check current status and outstanding requirements.

See the Reference sections for complete requirement lists.


1. Push: Webhook Events for Real-Time Updates

Subscribe to webhook events to receive real-time notifications when employer status changes. See Consuming Webhooks for setup instructions.

📘

Two types of webhooks

Lifecycle webhooks fire when entity state changes. Use these to keep your systems in sync — update your CRM, refresh dashboards, maintain an accurate projection of employer data.

Notification webhooks fire when something needs attention. Use these to trigger communications — send emails, create support tickets, alert your team. These are purpose-built for partner-side actions.

Lifecycle events

EventTrigger
Employer.PayWorkersReadiness.statusChangedReadiness status transitions
Employer.PayWorkersReadiness.requirementsChangedRequirements added/removed (fires even if status unchanged)
Employer.AccountStanding.statusChangedAccount standing transitions
Employer.AccountStanding.requirementsChangedAccount standing requirements changed
Employer.onboardingStatusHosted onboarding status changes
Employer.TaxesSetup.updatedTax configuration changed
Employer.Address.created/updated/deletedAddress changes
Employer.PayrollHistory.providedPrior payroll history provided
Employer.PayrollHistory.importCompletedPayroll history import finished
Employer.created/updated/deletedEmployer entity changes
EmployerBankAccount.created/updated/deletedBank account entity changes
EmployerBankAccount.Verification.statusChangedBank verification status changes
PayGroup.created/updated/deletedPay group changes

Notification events

EventTrigger
Notification.employerPayrollHistoryRequiredEmployer needs to provide payroll history
Notification.EmployerBankAccount.microDepositVerificationSentMicro-deposits initiated
Notification.EmployerBankAccount.microDepositVerificationCompletedBank verification succeeded
Notification.EmployerBankAccount.microDepositVerificationFailedBank verification failed

Event payloads

// Employer.PayWorkersReadiness.statusChanged
{
  "event": "Employer.PayWorkersReadiness.statusChanged",
  "employerId": "empr_abc123",
  "employerExternalId": "your-external-id",
  "status": "READY",
  "onboardedAt": "2024-01-15T10:30:00Z",
  "implementedAt": "2024-01-20T14:00:00Z"
}

// Employer.PayWorkersReadiness.requirementsChanged
{
  "event": "Employer.PayWorkersReadiness.requirementsChanged",
  "employerId": "empr_abc123",
  "employerExternalId": "your-external-id",
  "pendingRequirementsAdded": [],
  "pendingRequirementsRemoved": ["MISSING_VERIFIED_BANK_ACCOUNT"]
}

// Employer.AccountStanding.statusChanged
{
  "event": "Employer.AccountStanding.statusChanged",
  "employerId": "empr_abc123",
  "employerExternalId": "your-external-id",
  "status": "ACTIVE"
}

// Employer.AccountStanding.requirementsChanged
{
  "event": "Employer.AccountStanding.requirementsChanged",
  "employerId": "empr_abc123",
  "employerExternalId": "your-external-id",
  "pendingRequirementsAdded": [],
  "pendingRequirementsRemoved": ["MISSING_EMPLOYER_REGULATORY_SCREENING_REQUEST_FOR_INFORMATION"]
}

Best practices

  1. Event → API pattern — Fetch the full resource via GraphQL after receiving an event for complete data
  2. Subscribe selectively — Only subscribe to events you'll actually use
  3. Track granular progressrequirementsChanged events fire even when status stays the same

2. Pull: GraphQL Queries for Bulk Data

Query employers by status and requirements for dashboards, reports, and batch operations.

Available filters

filterBy: {
  # PayWorkersReadiness filters
  payWorkersReadinessStatuses: [ONBOARDING, IMPLEMENTATION, READY, PARTIALLY_READY]
  payWorkersReadinessRequirements: [MISSING_VERIFIED_BANK_ACCOUNT, INCOMPLETE_TAXES_SETUP, ...]

  # Account Standing filters
  accountStandingStatuses: [NEW_ACCOUNT, IN_REVIEW, ACTIVE, RESTRICTED, ...]
  accountStandingRequirements: [MISSING_EMPLOYER_BANK_ACCOUNT, EMPLOYER_FUNDING_FAILURE, ...]
}

Example queries

# Find employers stuck in onboarding or implementation
query StuckEmployers {
  employers(
    filterBy: { payWorkersReadinessStatuses: [ONBOARDING, IMPLEMENTATION] }
  ) {
    id
    businessName
    externalId
    createdAt
    capabilities {
      payWorkers { status, requirements { identifier } }
    }
  }
}

# Find employers with compliance issues
query ComplianceIssues {
  employers(
    filterBy: { accountStandingStatuses: [RESTRICTED, IN_REVIEW] }
  ) {
    id
    businessName
    accountStanding { status, requirements { code } }
  }
}

# Find employers missing bank verification
query MissingBankAccount {
  employers(
    filterBy: { payWorkersReadinessRequirements: [MISSING_VERIFIED_BANK_ACCOUNT] }
  ) {
    id
    businessName
    createdAt
  }
}

# Full onboarding dashboard
query OnboardingDashboard {
  employers {
    id
    businessName
    externalId
    createdAt
    capabilities {
      payWorkers { status, onboardedAt, implementedAt, firstTimeReadyAt, requirements { identifier } }
    }
    accountStanding { status, requirements { code } }
    onboardingSummary { status, completedAt }
  }
}

3. Recipes: Common Integration Patterns

Automated lifecycle communications

The problem: Employers get stuck during onboarding because they don't know what's required or forget to complete steps. Manual follow-up doesn't scale.

The opportunity: Automatically trigger emails at exactly the right moment — when an employer completes a step, when they need to take action, or when they've been stuck too long.

How to implement:

Subscribe to requirementsChanged events to detect progress, or use Notification webhooks for pre-defined communication triggers:

async function handleWebhook(event) {
  switch (event.event) {
    // Celebrate progress
    case 'Notification.EmployerBankAccount.microDepositVerificationCompleted':
      await sendEmail(event.employerId, 'bank-verified-congrats');
      break;

    // Prompt action needed
    case 'Notification.employerPayrollHistoryRequired':
      await sendEmail(event.employerId, 'import-payroll-history');
      break;

    // React to status changes
    case 'Employer.PayWorkersReadiness.statusChanged':
      if (event.status === 'READY') {
        await sendEmail(event.employerId, 'welcome-to-payroll');
      }
      break;
  }
}

Stuck employer detection

The problem: Some employers start onboarding but never finish. Without visibility, you don't know who's stuck, why, or how long they've been waiting.

The opportunity: Identify at-risk employers before they churn. Alert your team when someone needs help. Build targeted outreach for common blockers.

How to implement:

Run a daily query to find employers who haven't progressed:

async function detectStuckEmployers() {
  const employers = await graphql(`
    query { employers(filterBy: { payWorkersReadinessStatuses: [ONBOARDING, IMPLEMENTATION] }) {
      id, businessName, createdAt, capabilities { payWorkers { requirements { identifier } } }
    }}
  `);

  const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;

  return employers.filter(e => new Date(e.createdAt) < sevenDaysAgo);
}

Funnel analytics and conversion tracking

The problem: You don't know where your onboarding funnel is breaking down. Are employers getting stuck on bank verification? Tax setup? Payroll history import?

The opportunity: Build a real-time dashboard showing exactly where employers are in the funnel. Identify bottlenecks. Measure conversion rates. Make data-driven decisions about where to invest in improving the experience.

How to implement:

Query employers by status and aggregate:

async function getFunnelMetrics() {
  const [onboarding, implementation, ready] = await Promise.all([
    countEmployers({ payWorkersReadinessStatuses: ['ONBOARDING'] }),
    countEmployers({ payWorkersReadinessStatuses: ['IMPLEMENTATION'] }),
    countEmployers({ payWorkersReadinessStatuses: ['READY'] }),
  ]);

  return { onboarding, implementation, ready, conversionRate: ready / (onboarding + implementation + ready) };
}

CRM synchronization

The problem: Your sales and support teams work in your CRM, but employer onboarding data lives in Salsa. They can't see who's stuck or what's blocking them without switching systems.

The opportunity: Keep your CRM updated in real-time. Give your team instant visibility into every employer's status without leaving their workflow.

How to implement:

Subscribe to lifecycle events and sync to your CRM:

async function syncToCRM(event) {
  if (event.event.startsWith('Employer.PayWorkersReadiness')) {
    await crm.updateEmployer(event.employerExternalId, {
      payrollReadiness: event.status,
      pendingRequirements: event.pendingRequirementsAdded || []
    });
  }
}

Reference: Requirements

PayWorkersReadiness Requirements (examples)

RequirementDescription
MISSING_VERIFIED_BANK_ACCOUNTVerified bank account required
INCOMPLETE_TAXES_SETUPTax setup incomplete
MISSING_PAY_GROUP_WITH_RECURRING_SCHEDULEAt least one pay group required
PENDING_IMPORT_PRIOR_PAYROLL_HISTORY_DECLARATIONPayroll history import required

See the full list in the Capabilities API Reference.

Account Standing Requirements (examples)

RequirementDescription
MISSING_EMPLOYER_BANK_ACCOUNTBank account not set up
MISSING_EMPLOYER_REGULATORY_SCREENINGScreening not initiated
MISSING_EMPLOYER_REGULATORY_SCREENING_REQUEST_FOR_INFORMATIONAdditional info requested
EMPLOYER_FUNDING_FAILUREFunding issue

See the full list in the Webhooks Reference.


Need help?


Did this page help you?