Set up W-2 payroll

Confirm W-2 payroll is on for your account, add worksites, check your employee pay schedule and funding, and create Employee engagements in the V3 API.

Before you can assign an employee, your account needs an employer payroll profile, at least one worksite in a state you're registered in, an employee pay schedule, and an Employee engagement to assign people to. This page shows you how to set up each one, in that order.

Before you begin

Important: W-2 payroll is an add-on module. Your Wingspan team turns it on for your account. Until it's on, W-2 calls for your account don't succeed.

To turn it on, your Wingspan team needs your business's legal name and EIN (from your W-9). For each state where you'll employ people, they also need your state withholding account number, your state unemployment account number, your unemployment tax rate, and any state-specific IDs your Wingspan team asks for. Turning W-2 payroll on creates your employer payroll profile, sets up those states, and starts your employee pay schedule.

To employ people in another state later, send your Wingspan team that state's account numbers and rate.

Confirm W-2 payroll is on

Call GET /v3/payments/employer-payroll-profiles. An account with W-2 payroll returns exactly one profile. An empty data list means it isn't on yet.

curl https://api.wingspan.app/v3/payments/employer-payroll-profiles \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// 200 OK
{
  "data": [
    {
      "id": "Q7Ym1DivfmQPID03Y8ogD7",
      "accountId": "23Gvh0YtpH6bNj4iOxLqET",
      "metadata": {},
      "events": {
        "createdAt": "2026-11-02T15:04:11.000Z",
        "updatedAt": "2026-11-02T15:04:18.000Z"
      }
    }
  ],
  "pagination": { "nextPageToken": "" }
}

The profile is read-only. Wingspan keeps your state registrations and payroll tax filing details on it, and doesn't return them.

Add a worksite

A worksite is a place your employees work. The worksite on an employee's assignment decides which state taxes apply to their pay. Create one with POST /v3/payments/worksites.

curl -X POST https://api.wingspan.app/v3/payments/worksites \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Los Angeles office",
    "externalId": "site-la",
    "isPrimary": true,
    "address": {
      "line1": "350 S Grand Ave",
      "city": "Los Angeles",
      "subdivision": "CA",
      "postalCode": "90071",
      "countryCode": "US"
    }
  }'
// 201 Created (trimmed)
{
  "id": "RYQqqvt7IyyE1P5c3HDzmC",
  "accountId": "23Gvh0YtpH6bNj4iOxLqET",
  "name": "Los Angeles office",
  "address": {
    "line1": "350 S Grand Ave",
    "city": "Los Angeles",
    "subdivision": "CA",
    "postalCode": "90071",
    "countryCode": "US"
  },
  "isPrimary": true,
  "externalId": "site-la"
}

subdivision is the two-letter state code. The address can't change after you create the worksite, so create a new worksite when a location moves.

Mark one worksite as primary. When an employee has no home address on file, Wingspan uses your primary worksite as their residence for tax purposes. You can create a worksite in any US state, but you can only use it on an Employee engagement or assignment once that state is registered on your employer payroll profile. See Worksites for updating and deleting worksites.

Check your employee pay schedule

Turning on W-2 payroll starts your employee pay schedule. Read it with GET /v3/payments/payroll-schedules/Employee.

curl https://api.wingspan.app/v3/payments/payroll-schedules/Employee \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
// 200 OK (trimmed)
{
  "accountId": "23Gvh0YtpH6bNj4iOxLqET",
  "type": "Employee",
  "frequency": "Weekly",
  "interval": 1,
  "dayOfWeek": "Friday"
}

Employee schedules are Weekly with interval 1 (every week) or 2 (every two weeks), or Monthly with interval 1. You can change the schedule with PATCH /v3/payments/payroll-schedules/Employee while its first payroll run is still a draft. After that, the request returns 409. To see the pay periods the schedule will create, call POST /v3/payments/payroll-schedules/Employee/preview-pay-periods with a fromDate and toDate.

curl -X POST https://api.wingspan.app/v3/payments/payroll-schedules/Employee/preview-pay-periods \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "fromDate": "2026-11-06", "toDate": "2026-11-26" }'
// 200 OK
{
  "periods": [
    { "sequence": 1, "startDate": "2026-11-06", "endDate": "2026-11-12" },
    { "sequence": 2, "startDate": "2026-11-13", "endDate": "2026-11-19" },
    { "sequence": 3, "startDate": "2026-11-20", "endDate": "2026-11-26" }
  ]
}

Each Employee pay period is paid on the first Friday that leaves enough lead time for your funding source: one business day for a Wingspan Wallet, three for a bank account. With a Wallet, the Nov 6 to Nov 12 period is paid on Friday, Nov 13; with a bank account, on Friday, Nov 20. The run's approvalDeadline is that same lead time before payDate, and payDate on the run is the date to rely on. Payroll schedules covers both calls in detail.

To pause W-2 payroll, set employerPayrollSettings.status to Inactive with PATCH /v3/payments/payer-settings. Wingspan stops creating new pay periods. Pay periods and runs that already exist stay as they are, and you can still create off-cycle runs for them. Set it back to Active to resume.

Check your payroll funding source

Employee runs and contractor runs draw from the same payroll funding source, payrollSettings.defaultFundingSource on your payer settings. Wingspan debits it for each Employee run's gross pay plus your employer taxes. See Payroll settings and funding to read or change it.

Create an Employee engagement

An Employee engagement is a template for a W-2 role. Create one per role and worksite, such as "Field representative, Los Angeles", with POST /v3/payments/engagements.

curl -X POST https://api.wingspan.app/v3/payments/engagements \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Field representative, Los Angeles",
    "externalId": "eng-field-rep-la",
    "engagementType": "Employee",
    "worksiteId": "RYQqqvt7IyyE1P5c3HDzmC"
  }'
// 201 Created (trimmed)
{
  "id": "BmKH2r0PVv2JDxvmFA0fVn",
  "externalId": "eng-field-rep-la",
  "status": "Active",
  "name": "Field representative, Los Angeles",
  "isDefault": false,
  "engagementType": "Employee",
  "worksiteId": "RYQqqvt7IyyE1P5c3HDzmC",
  "rateCardId": "5EILQwBIyOgY0AhgANayBG",
  "workDefinitionIds": ["RG230mb8G9wPIKxfsUR3lu"],
  "requirementDefinitions": [
    { "requirementDefinitionId": "IXbayZvHRgm5s8WNHfhOiZ", "blockingMode": "NotBlocking" }
  ]
}

Leave worksiteId out for a remote role. Wingspan sets up three things on every new Employee engagement:

WhatName in the responseWhat it does
An hourly rate cardrateCardId, named "Hourly Rate Card (Default)"Holds two rates: hourlyRate (20) and overtimeRate (30). Change them to your pay rates with PATCH /v3/payments/rate-cards/{rateCardId}.
An hourly work definitionworkDefinitionIds, named "Hourly (Default)"Defines what an employee logs: hours (required, at least 1) and overtimeHours (optional).
The Employee tax forms requirementrequirementDefinitions, type EmployeeTaxFormsAsks each employee for their Form W-4 and state withholding certificate. At a California worksite it's NotBlocking: pay isn't held while it's open, and withholding uses the default elections. At other worksites it blocks payment until it's complete.

You can't create or remove the Employee tax forms requirement yourself. You can add your own requirements, such as a background check or a signed policy, the same way you do for contractors. See Requirements and eligibility.

To pay one employee a different rate from the rest of the engagement, set a rate card override on their assignment with PATCH /v3/payments/payees/{payeeId}/engagements/{payeeEngagementId}/rate-card.

Your account's default engagement must be a Contractor engagement, so you can't set isDefault: true on an Employee engagement.

What can go wrong

Status and codeCauseFix
Empty data from employer payroll profilesW-2 payroll isn't on for this account.Ask your Wingspan team to turn it on.
404 on the employee pay scheduleThe account has no employer payroll profile, or its pay schedule hasn't been started.Ask your Wingspan team to turn on W-2 payroll.
409 on PATCH .../payroll-schedules/EmployeeThe schedule's first run is past draft, so the cadence is locked.Keep the schedule, or contact support to change it.
422 payments.WorksiteJurisdictionNotEnabledThe worksite's state isn't registered on your employer payroll profile.Send your Wingspan team that state's account numbers and rate, or use another worksite.
422 payments.WorksiteJurisdictionUnsupported or payments.WorksiteCountryUnsupportedThe worksite is somewhere W-2 payroll doesn't cover.Use a worksite in a supported US state, or pay the worker as a contractor.
422 payments.DefaultEngagementMustBeContractorYou sent isDefault: true on an Employee engagement. Only a Contractor engagement can be the default.Leave isDefault out.

Related pages


Did this page help you?