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:
- PayWorkersReadiness — Operational readiness (can this employer pay workers?)
- 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
| Status | Description | What happens next |
|---|---|---|
ONBOARDING | Employer hasn't completed initial setup | Complete hosted employer onboarding |
IMPLEMENTATION | Employer has prior payroll history to import | Import payroll data from prior provider |
READY | Employer is ready to run payroll | No action needed |
PARTIALLY_READY | Minor issues exist but payroll can run | Address outstanding requirements |
Typical transitionsMost employers follow one of two paths:
ONBOARDING→READY(no prior payroll history)ONBOARDING→IMPLEMENTATION→READY(with prior payroll history)
Account Standing Phases
| Status | Description | Typical duration |
|---|---|---|
NEW_ACCOUNT | Employer needs to provide information for screening | Until info submitted |
IN_REVIEW | Pending Salsa action on provided information | 1-3 business days |
ACTIVE | Employer and workers are compliant | Ongoing |
RESTRICTED | More information required to pass screening | Until info provided |
REJECTED | Employer or workers failed screening | Terminal state |
CONDITIONAL | Approved with conditions/monitoring | Ongoing with review |
CANCELLED | Employer account cancelled | Terminal state |
ImportantEmployers in
RESTRICTEDorIN_REVIEWstatus 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 webhooksLifecycle 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
| Event | Trigger |
|---|---|
Employer.PayWorkersReadiness.statusChanged | Readiness status transitions |
Employer.PayWorkersReadiness.requirementsChanged | Requirements added/removed (fires even if status unchanged) |
Employer.AccountStanding.statusChanged | Account standing transitions |
Employer.AccountStanding.requirementsChanged | Account standing requirements changed |
Employer.onboardingStatus | Hosted onboarding status changes |
Employer.TaxesSetup.updated | Tax configuration changed |
Employer.Address.created/updated/deleted | Address changes |
Employer.PayrollHistory.provided | Prior payroll history provided |
Employer.PayrollHistory.importCompleted | Payroll history import finished |
Employer.created/updated/deleted | Employer entity changes |
EmployerBankAccount.created/updated/deleted | Bank account entity changes |
EmployerBankAccount.Verification.statusChanged | Bank verification status changes |
PayGroup.created/updated/deleted | Pay group changes |
Notification events
| Event | Trigger |
|---|---|
Notification.employerPayrollHistoryRequired | Employer needs to provide payroll history |
Notification.EmployerBankAccount.microDepositVerificationSent | Micro-deposits initiated |
Notification.EmployerBankAccount.microDepositVerificationCompleted | Bank verification succeeded |
Notification.EmployerBankAccount.microDepositVerificationFailed | Bank 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
- Event → API pattern — Fetch the full resource via GraphQL after receiving an event for complete data
- Subscribe selectively — Only subscribe to events you'll actually use
- Track granular progress —
requirementsChangedevents 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)
| Requirement | Description |
|---|---|
MISSING_VERIFIED_BANK_ACCOUNT | Verified bank account required |
INCOMPLETE_TAXES_SETUP | Tax setup incomplete |
MISSING_PAY_GROUP_WITH_RECURRING_SCHEDULE | At least one pay group required |
PENDING_IMPORT_PRIOR_PAYROLL_HISTORY_DECLARATION | Payroll history import required |
See the full list in the Capabilities API Reference.
Account Standing Requirements (examples)
| Requirement | Description |
|---|---|
MISSING_EMPLOYER_BANK_ACCOUNT | Bank account not set up |
MISSING_EMPLOYER_REGULATORY_SCREENING | Screening not initiated |
MISSING_EMPLOYER_REGULATORY_SCREENING_REQUEST_FOR_INFORMATION | Additional info requested |
EMPLOYER_FUNDING_FAILURE | Funding issue |
See the full list in the Webhooks Reference.
Need help?
- Capabilities API Reference
- Consuming Webhooks
- Onboarding Employers
- GraphQL Explorer
- Contact your Salsa integration engineer
Updated about 3 hours ago
