Set up employer pay types
Give each employer their own named pay types under a partner pay type you control, so the pay they see in Salsa matches the pay they configured in your platform.
Who this guide is for
You are a partner engineering team already sending pay to Salsa, and partner pay types alone are not enough for your employers. This is common when your platform lets each employer define their own pay structure — their own rate labels, service categories, or job codes — with names they choose themselves.
Read Payroll Elements first. This guide assumes you understand Types, Policies, and Entries, and picks up where that page leaves off.
Do you need employer pay types?
Partner pay types are provisioned once and shared by every employer you bring to Salsa. Employer pay types are per-employer subtypes underneath one of your partner pay types, created by your platform.
Learn more about payroll elements at Salsa.
| If this is true | Use |
|---|---|
| Every employer pays the same handful of pay types, with the same names | Partner pay types only |
| Employers need pay types they name themselves | Employer pay types |
| A worker earns several different rates that must appear separately | Employer pay types |
| Two pay types are taxed identically but must be reported separately | Employer pay types |
| The number of pay types varies per employer and changes over time | Employer pay types |
| You need a new tax treatment or compliance category | A new partner pay type |
The last row matters. Employer pay types do not change tax treatment. Tax treatment comes from the jurisdictional type at the top of the chain, which your partner pay type points at. An employer subtype is a naming and reporting layer, not a compliance layer. If two pay types need different tax handling, they need different partner pay types.
Two shapes that do not work
Both of these come up before employer pay types do, and both fail for reasons worth knowing.
One partner pay type for everything. Map every employer label onto a single Hourly partner pay type and each entry collapses into one line. The employer cannot tell which hours were paid at which rate without leaving Salsa, and the labels they chose never appear anywhere.
One partner pay type per label. Employer labels are employer-specific and change over time, so this means an unbounded number of partner pay types, name collisions between employers who use the same label, and a provisioning request to Salsa every time one of them adds a label. Partner pay types are not partner-creatable, so that request is a dependency on Salsa, not a call you can make.
The shape that works is one partner pay type per tax treatment, with per-employer subtypes underneath. Note where the naming convention comes from: two employers will use the same label, and eventually one of them will call theirs Hourly, colliding with the parent. Employers typically need four to six subtypes; some need nine or more, which is why this belongs at the employer layer and not the partner layer.
The shape of the solution
Jurisdictional pay type (Salsa — defines tax treatment)
└── Partner pay type (you — e.g. "Hourly", one per tax treatment)
├── Employer pay type (per employer — e.g. "Caregiving")
├── Employer pay type (per employer — e.g. "Office Admin")
└── Employer pay type (per employer — e.g. "Training")Each employer gets their own set of subtypes. Employer A's "Caregiving" and Employer B's "Field Work" are separate records that both inherit the tax treatment of your Hourly partner pay type.
Before you start
You MUST have:
- A partner pay type to hang subtypes from. Salsa provisions these during account setup. Use Retrieve all PartnerPayTypes to see what you have. If you need a new one, contact Salsa — partner pay types are not partner-creatable.
- The right amount strategy on that partner pay type. Subtypes inherit
allowedAmountStrategiesfrom the parent and cannot override it. An hourly parent should be rate-based so that hours and rate are captured per entry. - A place to store Salsa IDs. See Step 3 — this is the step partners most often underestimate.
Step 1 — Discover your partner pay types
Endpoint: GET /api/rest/v1/partner-pay-types (reference)
{
"data": [
{
"id": "paytype_9f2c...",
"name": "Hourly",
"referenceId": "ref:pay:yourpartner:us:hourly"
}
]
}Take the id of the partner pay type you want employer subtypes to inherit from. That value becomes parentTypeId in the next step.
Step 2 — Create an employer pay type
Endpoint: POST /api/rest/v1/employers/{employerId}/employer-pay-types (reference)
The request body has exactly two required fields.
{
"name": "Caregiving",
"parentTypeId": "paytype_9f2c..."
}Response:
{
"data": {
"id": "emppaytype_4b71...",
"employerId": "empr_1a2b...",
"name": "Caregiving",
"parentTypeId": "paytype_9f2c..."
}
}Repeat once per pay type the employer needs. Do this when you onboard the employer, and again whenever they add a pay type in your platform.
To read back what exists, use Retrieve all EmployerPayTypes — GET /api/rest/v1/employers/{employerId}/employer-pay-types.
Naming: read this before you create anything
Names are permanentNames are enforced unique across a shared namespace, and they are effectively permanent. Get the convention right before the employer's first payroll run.
| Rule | What you get if you break it |
|---|---|
| A subtype MUST NOT reuse the name of any partner pay type — including its own parent | A partner pay type with the same name already exists: ... |
| A subtype MUST NOT reuse the name of another subtype on the same employer | There exists another Employer Pay Type with the same name: ... |
| A subtype's name MUST be final before its first payroll run | This Pay Type cannot be modified because it has been processed in at least one payroll run. |
The first rule bites immediately in practice: an employer whose own label is literally "Hourly" cannot have a subtype named "Hourly" if your partner pay type is also called "Hourly". Adopt a naming convention that can never collide — qualify the employer's label, for example Hourly – Caregiving, or append a suffix your platform controls.
The third rule is the one to design around. PATCH .../employer-pay-types/{payTypeId} (reference) accepts only name, and it is rejected outright once any entry on that subtype has been processed in a payroll run. On an entry-driven integration that happens on the employer's very first payroll. You get one chance to name a pay type, and that name appears on payroll runs and worker payment records permanently.
Recommendation: confirm the employer's naming with them before their first run, and treat a rename in your platform as create a new subtype and stop sending to the old one, not as an update.
Step 3 — Store the mapping
Employer pay types have no referenceId. This is the single most important difference from partner pay types, and it shapes your integration.
| Partner pay type | Employer pay type | |
|---|---|---|
| Stable reference | referenceId, e.g. ref:pay:yourpartner:us:hourly | ❌ none |
| Portable across Sandbox and Production | ✅ yes | ❌ no — IDs differ per environment |
| Partner-supplied external ID | ❌ no | ❌ no |
| Addressable by | referenceId or id | id only |
So you MUST persist, in your own system, a mapping from your pay-type concept to the Salsa EmployerPayType.id, keyed per employer and per environment:
| Your employer | Your pay type label | Environment | Salsa employerPayTypeId |
|---|---|---|---|
agency-4417 | Caregiving | Sandbox | emppaytype_4b71... |
agency-4417 | Caregiving | Production | emppaytype_c093... |
agency-4417 | Office Admin | Production | emppaytype_88ae... |
Populate this table at creation time from the POST response, and reconcile it with GET .../employer-pay-types so you can recover if a write is lost. Do not hardcode IDs and do not assume a Sandbox ID resolves in Production.
Step 4 — Send pay entries to the employer pay type
Use the same Paystream call you use today. The only change is the value of payReferenceId.
payReferenceId accepts either a partner referenceId or a pay type id — partner or employer. Put the employer pay type's id there and the entry lands on that subtype.
Endpoint: POST /api/rest/v1/paystream/payroll-elements (reference)
{
"data": {
"type": "PaystreamPayrollElementPeriodReplacementInput",
"periodStartDate": "2026-09-01",
"periodEndDate": "2026-09-15",
"employerId": "agency-4417",
"workers": [
{
"workerId": "wkr_def456",
"pay": [
{
"payReferenceId": "emppaytype_4b71...",
"startDateTime": "2026-09-02T09:00:00-05:00",
"endDateTime": "2026-09-02T14:00:00-05:00",
"rate": "19.00",
"externalId": "shift-88213"
},
{
"payReferenceId": "emppaytype_88ae...",
"startDateTime": "2026-09-02T15:00:00-05:00",
"endDateTime": "2026-09-02T18:00:00-05:00",
"rate": "16.00",
"externalId": "shift-88219"
}
]
}
]
}
}Three rules to follow here:
- Pick one addressing form per pay type and never mix them. Overlap detection groups entries by the raw
(workerId, payReferenceId)pair, so sending the same effective pay type sometimes as a partnerreferenceIdand sometimes as an employeridcan defeat it. includePayReferenceIdsmust echo exactly what you sent. If you use the period-replacement filter, every value in yourpayarray must appear in the include list, employer IDs included, or the request fails withInclude pay reference ids must match all pay reference ids provided.- Errors are batch-fatal and partner-worded. An unresolvable reference rejects the whole payload — no partial success — and the message says
Cannot find compensation with reference id: ...orCould not find a partner compensation policy for reference id ...even when the miss is an employer pay type ID. Do not let the wording send you looking at partner types.
You do not need worker pay policies
A Paystream pay entry is sufficient on its own — its effectiveInterval is what pulls it into a payroll run, and no WorkerPayPolicy is needed. So adopting employer pay types is a per-employer setup step, not a per-worker migration; pay types would otherwise have to be assigned to workers one at a time.
Step 5 — Stay in sync
You only need to track the subtypes your platform created. If you are posting hours to Salsa, you are also authoring the pay types those hours land on, so your mapping table covers your own subtypes and nothing else.
An employer may also create their own subtypes under the same partner pay type, for pay your platform does not manage. Those are the employer's to configure — they carry their own employer and worker policies, and the employer enters that pay in Salsa directly. You can leave them alone: they have no effect on the entries you send, and nothing in your integration needs to know they exist.
That makes the Employer.PayType webhooks a drift and confirmation signal for the subtypes you own, not an inbound sync to reconcile against:
| Event | Act on it by |
|---|---|
Employer.PayType.created | Confirming a subtype you created. One you did not create is the employer's own — no action needed |
Employer.PayType.updated | Refreshing the stored name for a subtype you own |
Employer.PayType.deleted | Removing the mapping and stopping sends to that ID — the one event you should always handle |
See Webhooks for endpoint setup.
A rename in your platform cannot be synced
If your platform lets an employer rename a pay type, do not try to mirror that rename with PATCH. Once any entry on the subtype has been processed in a payroll run — for an entry-driven integration, the first payroll after adoption — the call is rejected with This Pay Type cannot be modified because it has been processed in at least one payroll run. Retrying will not help, and the name is fixed from that point on.
Treat a rename as a versioned replacement instead: create a new subtype, repoint your mapping, and stop sending to the old ID. See Handling renames in your platform below.
Constraints to design around
Overlapping entries and hours
Splitting one pay type into several changes how overlapping shifts are countedRead this section if your platform can report concurrent or overlapping work for one worker — live-in care, one caregiver serving two clients in the same window, cross-midnight shifts.
allowOverlappingEntriesis resolved by walking the parent chain. A subtype's own value wins when set; otherwise the nearest ancestor's value applies; otherwisefalse. In practice, set it on the partner pay type and let subtypes inherit — it is not settable onCreateEmployerPayTypeInput.- When it is
false, overlapping entries are rejected, grouped by(workerId, payReferenceId):Overlapping pay entry intervals found with referenceId: ... for worker: ... - When it is
true, hours are merged per pay type so overtime thresholds reflect unique time worked — but intervals from different pay types do not merge with each other.
That last point is the risk. Two overlapping shifts on one pay type collapse to unique wall-clock time. Move them to two different subtypes and the same minute counts twice toward daily and weekly overtime thresholds. Model your real overlap patterns before you split, and either eliminate overlap upstream or confirm the overtime outcome with Salsa first.
Time worked is set at the partner level
isIncludedInTimeWorked — which decides whether a pay type's hours count as time worked, and so feeds anywhere hours worked are considered: overtime, premium eligibility, hours-based time-off accrual, and any hours-driven tax or reporting — exists only on the partner pay type, and subtypes inherit it with no override. All subtypes of one partner pay type therefore share one time-worked behavior. You cannot make Hourly – Caregiving count toward overtime while Hourly – Training does not; that requires two partner pay types.
If overtime is silently absent, this flag is the first thing to check — a worker with no hourly compensation flagged isIncludedInTimeWorked is skipped without an error.
Deletion
A subtype can be deleted only while nothing references it. Deletion fails once an employer policy, a worker policy, a worker pay entry, or a historical worker payment exists for it. After the first payroll run a pay type cannot be deleted at all — it must be marked inactive by Salsa Payroll Support. This is deliberate, so that historical payroll records stay intact.
Plan for pay types to be added and retired, never removed.
Handling renames in your platform
Because a processed subtype's name is immutable, a rename on your side cannot be mirrored with PATCH. Handle it as a versioned replacement:
- Create a new employer pay type with the new name.
- Point your mapping at the new ID.
- Stop sending entries to the old ID, and leave it in place for history.
The employer will see both names on year-to-date figures for the remainder of the tax year. Tell them that before the rename, not after.
Migrating an employer that already ran payroll
Adopt employer pay types forward-only. Entries already processed under the partner pay type stay there; you cannot move them. Expect the employer's year-to-date view and earnings statements to show the old partner pay type alongside the new subtypes until the next tax year, and set that expectation with them before the first run on the new shape.
What employer pay types do not change
- Tax treatment, which comes from the jurisdictional type via your partner pay type.
- Allowed amount strategies, inherited from the parent and not overridable.
- Rate-based configuration (
rateBasedConfiguration), set at the partner level and read through to subtypes. You never set it on an employer pay type. - Overtime arithmetic. Splitting one partner pay type into subtypes does not add to or remove from the FLSA regular-rate calculation. Where a worker has several rates in a week, a blended regular rate still applies where the jurisdiction requires it.
- How pay entries reach Salsa. You send them through Paystream, exactly as before — the only change is the
payReferenceIdvalue.
Implementation checklist
- Confirm the parent partner pay type exists and its amount strategy is right for the pay you send
- Agree a subtype naming convention that cannot collide with any partner pay type name
- Build the per-employer, per-environment
employerPayTypeIdmapping table - Create subtypes at employer onboarding, and on every pay type your platform adds
- Switch
payReferenceIdto the employer pay typeid, consistently, per pay type - Update
includePayReferenceIdsif you use period-replacement filtering - Track only the subtypes your platform created, and use the
Employer.PayTypewebhooks as a drift signal on those - Decide
allowOverlappingEntrieson the parent, and validate overtime against your real overlap patterns - Verify end to end in Sandbox before Production — IDs do not carry across environments
- Agree naming with each employer before their first payroll run
Related
- Payroll Elements — Types, Policies, and Entries
- Amount strategies — fixed, percentage, rate-based
- Set up and pay Overtime
- External IDs & Mapping to Salsa
- Send Paystream item and Import payroll elements
Updated about 6 hours ago
