Worker Data via Paystream

Overview

Paystream is Salsa's data ingestion API that allows you to create and update worker records, including personal information, addresses, bank accounts, contracts, and more. This guide explains what worker data you can send through Paystream, what can be updated, and important limitations to be aware of.

For the end-to-end sync pattern (employer first, then workers, and which system owns each field), see Paystream.

Worker Data Categories

Worker data in Paystream is organized into the following categories:

CategoryWhat It IncludesCreateUpdate
Basic InformationFirst name, last name, middle name, display name✓✓
Personal InformationEmail, birth date, SSN/SIN, phone number, business contractor info✓✓
Home AddressFull address details including street, city, state, zip, country✓✓
Bank AccountsAccount type, routing number, account number, payment distribution✓⚠️ Limited
ContractClassification (employee/contractor), hire date, overtime eligibility✓❌ No
TerminationTermination date, category, and notes✓✓

Creating vs Updating Workers

How Upsert Works

Paystream uses an upsert pattern - the same endpoint handles both creating new workers and updating existing ones. The system determines whether to create or update based on the presence of the Salsa worker ID:

  • Include id (Salsa's worker ID) → Updates the existing worker
  • Omit id → Creates a new worker
📘

Use External IDs for Your Reference

Include an externalId from your system when creating workers. Worker external IDs must be unique across all workers, not just within one employer; if yours are only unique per employer, prefix them with the employer ID. This helps you maintain the mapping between your system and Salsa. Both IDs (workerId from Salsa and your externalId) will be returned in the response.

Synchronous vs Asynchronous Processing

Paystream offers two approaches for ingesting worker data:

Synchronous (Recommended for most use cases)

Endpoint: POST /v1/paystream/workers
Scope: PAYSTREAM_WORKER_WRITE

Returns results immediately with all created/updated worker IDs:

{
  "data": [
    {
      "workerId": "worker_xyz789",
      "externalId": "your-system-id-123",
      "workerContract": {
        "id": "contract_abc456"
      },
      "bankAccounts": [
        {
          "id": "bankaccount_def789",
          "externalId": "bank-external-123"
        }
      ]
    }
  ]
}

Use when: You need immediate confirmation and IDs for further processing.

A request where some workers fail and others succeed still returns 201, with one entry per failed worker in errors. Always read errors, not only the status code.

Asynchronous ("Send and Forget")

Endpoint: POST /v1/paystream
Scope: PAYSTREAM_ASYNC_WRITE

Queues items for background processing and returns immediately. Add ?synchronous=true to process the items inline instead.

Use when: Processing large batches where you don't need immediate results.

Getting Results: Subscribe to webhook events to receive workerId and externalId when processing completes:

{
  "eventType": "Worker.created",
  "data": {
    "workerId": "worker_xyz789",
    "workerExternalId": "your-system-id-123",
    "employerId": "employer_abc123",
    "employerExternalId": "your-employer-ref"
  }
}
💡

Webhook Tip

Webhook events include both Salsa IDs and your external IDs, making it easy to create mappings in your database without additional API calls.

Example: Creating a Worker

{
  "data": [
    {
      "type": "PaystreamWorkerUpsertInput",
      "employerId": "employer_abc123",
      "externalId": "your-system-worker-456",
      "firstName": "Jane",
      "lastName": "Doe",
      "displayName": "Jane Doe",
      "personalInfo": {
        "email": "[email protected]",
        "birthDate": "1990-05-15",
        "individualIdentificationNumber": "123-45-6789",
        "phoneNumber": {
          "number": "(555) 123-4567",
          "countryCode": "+1"
        }
      },
      "homeAddress": {
        "name": "Home",
        "addressLine1": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "postalCode": "94105",
        "countryISO": "USA"
      },
      "contract": {
        "classification": "EMPLOYEE",
        "effectiveDate": "2024-01-01",
        "overtimeEligible": true,
        "hireDate": "2024-01-01"
      }
    }
  ]
}

Example: Updating a Worker

{
  "data": [
    {
      "type": "PaystreamWorkerUpsertInput",
      "id": "worker_xyz789",
      "employerId": "employer_abc123",
      "personalInfo": {
        "email": "[email protected]",
        "phoneNumber": {
          "number": "(555) 987-6543",
          "countryCode": "+1"
        }
      },
      "homeAddress": {
        "name": "Home",
        "addressLine1": "456 Oak Avenue",
        "city": "Oakland",
        "state": "CA",
        "postalCode": "94612",
        "countryISO": "USA"
      }
    }
  ]
}

What You Can Update

Fully Updatable

These fields can be freely updated at any time:

Basic Information

  • First name, middle name, last name
  • Display name
📘

Tip: Updating Names

You can update firstName and lastName independently. If you only send firstName, the lastName remains unchanged (and vice versa). displayName is not regenerated, so send it too if it should change.

Personal Information

  • Email address
  • Birth date
  • SSN/SIN (individual identification number)
  • Phone number
  • Business contractor information (EIN/BN, business name)

Address

  • All address fields (street, city, state, zip, country)
  • Address is always replaced in full - partial updates are not supported

Termination

  • Termination date, category, and employer notes
  • Used to mark a worker as terminated

Limited Update Capability

Bank Accounts

Bank accounts have special update rules:

  • Before first use: Can be updated freely
  • After use in payroll: Become immutable
  • Attempting to update a used account: Creates a new bank account instead of updating the existing one

Best Practice: Always create a new bank account when changes are needed rather than attempting to update an existing one.

Setting Default Payment Account:

{
  "bankAccounts": [
    {
      "accountType": "CHECKING",
      "routingNumber": "123456789",
      "accountNumber": "987654321",
      "displayName": "Primary Checking",
      "setAsExclusivePayDistributionRule": true
    }
  ]
}
⚠️

Important: Exclusive Payment Distribution

Setting setAsExclusivePayDistributionRule: true replaces ALL existing payment distribution rules and makes this bank account the sole recipient of funds. Only one bank account per request can have this flag set to true.

Cannot Be Updated

Worker Contract

Worker contracts can only be created during initial worker creation. They cannot be updated via Paystream.

Contract fields (create-only):

  • Classification (EMPLOYEE or CONTRACTOR)
  • Effective date
  • Overtime eligibility
  • Hire date
  • End date
🚧

Overtime eligibility drives overtime calculation

A worker created with overtimeEligible: false (or without a value) is never evaluated for overtime, and payroll runs do not flag it. Set it when you create the worker, or confirm the employer completes it before the first payroll run. See Who gets overtime.

🚫

Contract Updates

To update a worker's contract after creation, you must use the Worker Contract API (not Paystream). Attempting to update a contract via Paystream will return an error.

Why This Restriction?

Worker contracts are foundational to payroll calculations and tax compliance. Changes to classification, overtime eligibility, or contract dates require careful handling through the dedicated Advanced API to ensure proper tax calculations and compliance. Let us know if you want to own the contract data, and we will help you.

Special Data Types

Business Contractors

Workers classified as contractors can operate as a business entity:

{
  "contract": {
    "classification": "CONTRACTOR"
  },
  "personalInfo": {
    "isBusinessContractor": true,
    "businessIdentificationNumber": "12-3456789",
    "businessName": "Jane Doe Consulting LLC",
    "individualIdentificationNumber": null
  }
}

When isBusinessContractor: true:

  • Use businessIdentificationNumber (EIN/BN) instead of individualIdentificationNumber (SSN/SIN)
  • Provide businessName for the business entity
  • Taxes will be filed under the business entity

Worker Termination

To terminate a worker, send termination details:

{
  "termination": {
    "terminationDate": "2024-12-31",
    "category": "VOLUNTARY",
    "employerNotes": "Resigned for new opportunity"
  }
}

Termination Categories:

  • VOLUNTARY - Worker resigned
  • INVOLUNTARY - Worker was terminated
  • RETIREMENT - Worker retired
  • DECEASED - Worker deceased
  • OTHER - Other reason

Best Practices

1. Store Both Salsa Worker IDs and Your External IDs

When you create a worker, the response includes Salsa's workerId. You must store this ID in your system to perform updates later.

// Response from creation:
{
  "workerId": "worker_xyz789",
  "externalId": "your-system-id-123"
}

Store both IDs:

  • workerId - Required for updates via Paystream
  • externalId - Your reference for mapping between systems

2. Know Which Updates Are Partial

Updates are partial for names and personal information, but not for the address:

  • Names: firstName, middleName, lastName and displayName you omit keep their current value. displayName is not regenerated when you change a name, so send it too if it should change.
  • Personal information: fields you omit (birthDate, email, phoneNumber, identification numbers) are left unchanged. Omitting a field never clears it.
  • Home address: send the complete address. It replaces the stored address in full.
{
  "personalInfo": {
    "email": "[email protected]"
  }
}

This changes only the email; the other personalInfo fields keep their values.

3. Handle Bank Accounts Immutability

Always create new bank accounts rather than attempting to update existing ones:

{
  "bankAccounts": [
    {
      "externalId": "bank-account-v2",
      "accountType": "CHECKING",
      "routingNumber": "987654321",
      "accountNumber": "123456789",
      "setAsExclusivePayDistributionRule": true
    }
  ]
}

Use externalId to track different versions of bank accounts in your system.

Related Resources

  • Paystream - Overview, endpoint choice, and syncing employer and worker data
  • Paystream API Reference - Complete API reference with all available fields
  • Webhook Events - Subscribe to worker creation and update events for async processing


Did this page help you?