---
# === IDENTITY ===
id: business/erp-integration/payroll-integration/2026
canonical_question: "How do you integrate HRIS (Workday, SuccessFactors) with payroll providers and GL posting?"
aliases:
  - "HRIS to payroll integration architecture"
  - "payroll to general ledger journal posting automation"
  - "multi-country payroll integration playbook"
  - "earnings and deductions mapping payroll to ERP"
entity_type: erp_integration
domain: business > erp-integration > payroll-integration
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Workday HCM + Payroll"
    vendor: "Workday"
    version: "2025R2"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "SOAP (WWS), REST, RaaS"
  - name: "SAP SuccessFactors Employee Central Payroll"
    vendor: "SAP"
    version: "H2 2025"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "OData, SOAP, SFTP"
  - name: "ADP Workforce Now / Vantage HCM"
    vendor: "ADP"
    version: "API v2"
    edition: "Enterprise / Vantage"
    deployment: cloud
    api_surface: "REST (JSON)"
  - name: "Paylocity Web Pay"
    vendor: "Paylocity"
    version: "API v2025"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST (JSON)"
  - name: "Paychex Flex"
    vendor: "Paychex"
    version: "Flex API v1"
    edition: "Flex Enterprise"
    deployment: cloud
    api_surface: "REST (JSON)"

# === VERIFICATION ===
last_verified: 2026-03-03
confidence: 0.85
version: 1.0
first_published: 2026-03-03

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "ADP Pay Data Input v2 (2024-Q3); Workday 2025R1 REST API expansion"
  next_review: 2026-08-30
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Payroll runs are time-critical: GL posting must complete before period close or bank file cutoff"
  - "Tax filing deadlines are immovable: US W-2/941 quarterly, EU country-specific monthly/quarterly"
  - "Multi-country payroll requires country-specific earnings/deduction codes: no universal standard exists"
  - "ADP API rate limits: 30 requests/minute per endpoint; Paylocity: 120 requests/minute"
  - "Workday payroll journal posting requires pre-configured Worktags and Ledger Account mappings"
  - "SAP ECP GL posting uses symbolic accounts mapped to FI GL accounts: mismatch causes posting failures"
  - "Bank file formats vary by country: ACH (US), BACS (UK), SEPA (EU), country-specific for APAC/LATAM"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs only HRIS master data sync without payroll processing"
    use_instead: "business/erp-integration/workday-hcm-api/2026"
  - condition: "User needs time & attendance integration only"
    use_instead: "business/erp-integration/time-attendance-integration/2026"
  - condition: "User needs benefits enrollment integration only"
    use_instead: "business/erp-integration/benefits-integration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: payroll_provider
    question: "Which payroll provider(s) are you using?"
    type: choice
    options:
      - "Workday Payroll (native)"
      - "ADP Workforce Now / Vantage"
      - "Paylocity"
      - "Paychex Flex"
      - "SAP SuccessFactors Employee Central Payroll"
      - "Global payroll aggregator (CloudPay, Papaya Global, Deel)"
      - "Other / custom"
  - key: erp_target
    question: "Which ERP receives the GL journal entries?"
    type: choice
    options:
      - "SAP S/4HANA"
      - "Oracle ERP Cloud / NetSuite"
      - "Microsoft Dynamics 365 / Business Central"
      - "Workday Financials"
      - "Sage Intacct"
      - "QuickBooks / Xero"
      - "Other"
  - key: country_scope
    question: "How many countries does your payroll span?"
    type: choice
    options:
      - "Single country (US only)"
      - "2-5 countries"
      - "6-20 countries"
      - "> 20 countries (global)"
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "batch (post-payroll-run GL file)"
      - "real-time (per-transaction posting)"
      - "event-driven (payroll-complete webhook)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/payroll-integration/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-03-03)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "business/erp-integration/workday-hcm-api/2026"
      label: "Workday HCM API capabilities (employee master data source)"
  related_to:
    - id: "business/erp-integration/successfactors-ec-api/2026"
      label: "SAP SuccessFactors Employee Central API"
    - id: "business/erp-integration/adp-api-capabilities/2026"
      label: "ADP API capabilities and rate limits"
  solves:
    - id: "business/erp-integration/period-close-automation/2026"
      label: "Financial period close automation playbook"
  alternative_to:
    - id: "business/erp-integration/payroll-gl-file-based/2026"
      label: "File-based payroll-to-GL posting (CSV/flat file)"

# === SOURCES ===
sources:
  - id: src1
    title: "Workday Web Services API Documentation"
    author: Workday
    url: https://community.workday.com/sites/default/files/file-hosting/productionapi/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src2
    title: "ADP API Developer Portal - Pay Data Input v1"
    author: ADP
    url: https://developers.adp.com/
    type: official_docs
    published: 2025-06-15
    reliability: authoritative
  - id: src3
    title: "Paylocity Developer Portal - Pay Setup & Earnings API"
    author: Paylocity
    url: https://developer.paylocity.com/integrations/docs/pay-setup-deductions-earnings
    type: official_docs
    published: 2025-03-11
    reliability: authoritative
  - id: src4
    title: "SAP SuccessFactors Employee Central Payroll Integration Guide"
    author: SAP
    url: https://help.sap.com/docs/SAP_SUCCESSFACTORS_EMPLOYEE_CENTRAL_PAYROLL
    type: official_docs
    published: 2025-11-01
    reliability: authoritative
  - id: src5
    title: "Paychex Developer Program API Documentation"
    author: Paychex
    url: https://developer.paychex.com/
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src6
    title: "The Ultimate Guide to Payroll Integration"
    author: Flexspring
    url: https://www.flexspring.com/flexspring-news/the-ultimate-guide-to-payroll-integration
    type: technical_blog
    published: 2025-01-15
    reliability: moderate_high
  - id: src7
    title: "Payroll Journal Entry: Types, Examples & Best Practices"
    author: Ramp
    url: https://ramp.com/blog/payroll-journal-entry
    type: technical_blog
    published: 2025-04-10
    reliability: moderate_high
  - id: src8
    title: "Managing Multi-Country Payroll Compliance"
    author: CloudPay
    url: https://www.cloudpay.com/blog/managing-payroll-compliance-across-multiple-countries/
    type: industry_report
    published: 2025-09-01
    reliability: moderate_high
---

# HRIS-to-Payroll-to-GL Integration Playbook

## TL;DR

- **Bottom line**: Payroll integration is a 5-stage pipeline: employee master sync (HRIS) --> payroll calculation --> GL journal posting --> bank file generation --> tax filing. Each stage has different APIs, formats, and timing constraints.
- **Key limit**: GL posting must complete before financial period close; bank files must hit cutoff times (typically 2-4 PM local time for same-day ACH/BACS/SEPA).
- **Watch out for**: Earnings/deduction code mapping mismatches between HRIS and payroll provider -- this is the #1 cause of payroll integration failures and incorrect GL postings.
- **Best for**: Organizations running HRIS (Workday, SuccessFactors) with external payroll providers (ADP, Paylocity, Paychex) that need automated GL posting to an ERP.
- **Authentication**: All major providers use OAuth 2.0; ADP uses client credentials flow, Paylocity uses API key + secret, Paychex uses OAuth 2.0 authorization code flow.

## System Profile

This playbook covers the end-to-end integration flow from HRIS systems (Workday HCM, SAP SuccessFactors Employee Central) through payroll providers (ADP, Paylocity, Paychex, or native Workday/SAP payroll) to ERP general ledger posting. It applies to both single-country (US) and multi-country deployments. The playbook assumes a middleware/iPaaS layer (MuleSoft, Workato, Boomi, or custom) orchestrating the flow.

This card does NOT cover: standalone time & attendance integration, benefits-only integration, or contractor/1099 payment processing (different tax and filing requirements).

| System | Role | API Surface | Direction |
|---|---|---|---|
| Workday HCM / SF EC | HRIS -- employee master, org structure, compensation | SOAP (WWS) / OData v4 | Outbound (source of truth) |
| ADP Workforce Now | Payroll provider -- calc, tax, garnishments | REST API v2 (JSON) | Inbound (receives master) + Outbound (results) |
| Paylocity Web Pay | Payroll provider -- calc, tax, garnishments | REST API (JSON) | Inbound (receives master) + Outbound (results) |
| Paychex Flex | Payroll provider -- calc, tax, garnishments | REST API v1 (JSON) | Inbound (receives master) + Outbound (results) |
| SAP ECP | Native SF payroll -- calc, tax, posting | RFC/IDoc/OData | Bidirectional with SF EC |
| Workday Payroll | Native Workday payroll -- calc, tax, posting | Internal (no API needed) | Bidirectional with Workday HCM |
| Target ERP (SAP/Oracle/D365/Intacct) | Financial system -- GL, AP, bank reconciliation | OData/REST/SOAP/File | Inbound (receives journal entries) |
| iPaaS (MuleSoft/Workato/Boomi) | Orchestrator -- transforms, error handling, scheduling | N/A | Orchestrator |

## API Surfaces & Capabilities

| API Surface | Provider | Protocol | Best For | Auth | Rate Limit | Bulk? |
|---|---|---|---|---|---|---|
| Workday Web Services (WWS) | Workday | SOAP/XML | Employee data, payroll results, journal entries | WS-Security + OAuth 2.0 | Fair use / throttled | Yes (batch) |
| Workday REST API | Workday | HTTPS/JSON | Reports-as-a-Service (RaaS), worker data | OAuth 2.0 (JWT bearer) | Fair use / throttled | Limited |
| Workday RaaS | Workday | HTTPS (JSON/CSV/XML) | Custom payroll reports, GL extracts | OAuth 2.0 | Fair use | Yes |
| ADP Pay Data Input v1 | ADP | HTTPS/JSON | Batch earnings, deductions, reimbursements | OAuth 2.0 client credentials | 30 req/min per endpoint | Yes (batch payloads) |
| ADP Payroll Output API | ADP | HTTPS/JSON | Pay statements, check-level detail | OAuth 2.0 client credentials | 30 req/min per endpoint | Yes |
| Paylocity Pay Setup API | Paylocity | HTTPS/JSON | Earnings codes, deduction codes, pay frequencies | API key + secret | 120 req/min | No |
| Paylocity Pay Statements API | Paylocity | HTTPS/JSON | Post-run pay data, GL summary | API key + secret | 120 req/min | Yes |
| Paychex Flex API | Paychex | HTTPS/JSON | Worker data, pay components, check data | OAuth 2.0 auth code | Per-agreement | Limited |
| SAP ECP Integration | SAP | RFC/IDoc + OData | EC-to-ECP replication, GL posting to S/4HANA | SAML/OAuth 2.0 | System-dependent | Yes (IDoc batch) |
| SF EC OData API | SAP | OData v4 | Employee master, compensation, job info | OAuth 2.0 SAML bearer | 300 req/min (concurrent: 10) | Yes (batch) |

[src1, src2, src3, src4, src5]

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| ADP batch payload | 500 employees per batch | Pay Data Input v1 | Split larger payrolls into multiple batches |
| Paylocity response page size | 100 records per page | All list endpoints | Use pagination token for full dataset |
| Paychex response page size | 50 records default, 100 max | Worker/check endpoints | Set via `limit` query param |
| SAP ECP IDoc batch | 1,000 posting documents per run | GL posting to S/4HANA | Configure in posting run settings |
| Workday RaaS report | 100,000 rows per report execution | Custom reports | Use incremental/delta reports for larger datasets |

[src1, src2, src3, src5]

### Rolling / Daily Limits

| Limit Type | Value | Window | Provider |
|---|---|---|---|
| ADP API calls | 30 requests/minute per endpoint | Per minute | ADP Workforce Now |
| Paylocity API calls | 120 requests/minute | Per minute | Paylocity Web Pay |
| SF EC OData concurrent | 10 concurrent requests | Per tenant | SAP SuccessFactors |
| SF EC OData rate | 300 requests/minute | Per tenant | SAP SuccessFactors |
| Workday | Fair use / throttled | Dynamic | Workday (no published hard limit) |
| Paychex | Per agreement | Per agreement | Paychex Flex |

[src1, src2, src3, src4, src5]

## Authentication

| Provider | Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|---|
| ADP | OAuth 2.0 client credentials | Server-to-server integration | 60 minutes | New token per expiry | Requires ADP Marketplace registration |
| Paylocity | API key + secret (custom auth) | All API integrations | Session-based | Per request | Key/secret issued per company in developer portal |
| Paychex | OAuth 2.0 authorization code | User-context or server integrations | 60 minutes | Yes (refresh token) | Sandbox available for testing |
| Workday | OAuth 2.0 JWT bearer / WS-Security | RaaS + REST / SOAP respectively | Configurable (default 60 min) | Yes | ISU (Integration System User) required for unattended |
| SAP SF | OAuth 2.0 SAML bearer assertion | OData API calls | 60 minutes | Yes | Requires X.509 certificate for SAML assertion |

[src1, src2, src3, src4, src5]

### Authentication Gotchas

- ADP OAuth tokens are scoped per company code: multi-company integrations need separate tokens per legal entity. [src2]
- Paylocity API keys are company-specific: a payroll service bureau managing 50 clients needs 50 credential sets. [src3]
- Workday ISU accounts must have security group assignments for every business process involved in payroll -- missing a single security group causes silent data omission in reports. [src1]
- SAP SF SAML bearer flow requires the X.509 certificate to match between the OAuth client registration and the Identity Provider -- certificate rotation breaks integrations if not coordinated. [src4]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **GL posting timing**: Journal entries must post before period close. For monthly close, this means payroll-to-GL must complete within 1-2 business days of final payroll run.
- **Tax filing deadlines**: US federal 941 (quarterly), state SUI (quarterly), W-2 (annual Jan 31). EU varies by country. Missed deadlines = penalties.
- **Earnings code mapping is 1-to-many**: One HRIS earnings type (e.g., "Regular Pay") may map to multiple payroll provider codes depending on FLSA status, state, and tax jurisdiction.
- **GL account mapping must handle multi-entity**: A single payroll run for a multi-entity company must split journal entries by legal entity, cost center, and department.
- **Bank file cutoff times are immovable**: ACH same-day cutoff is typically 2:00 PM ET; BACS (UK) requires 3-day advance submission; SEPA (EU) varies 1-2 days.
- **Retroactive pay adjustments require reversal + re-posting**: Cannot simply update a posted journal entry; must create reversal entry + new entry.
- **Multi-country payroll has no universal API standard**: Each country has different pay components, tax structures, and statutory deductions. Global payroll aggregators (CloudPay, Papaya, Deel) abstract this but add a layer of API indirection.

## Integration Pattern Decision Tree

```
START -- Payroll Integration Architecture
|
+-- What is the HRIS-to-Payroll relationship?
|   +-- Native payroll (Workday Payroll, SAP ECP)
|   |   +-- No integration needed for employee data
|   |   +-- GL posting: Workday = Financial Accounting journal task
|   |   +-- GL posting: SAP ECP = IDoc/posting run to S/4HANA
|   |   +-- Go to GL POSTING section
|   +-- Third-party payroll (ADP, Paylocity, Paychex)
|   |   +-- Employee master sync needed: HRIS --> Payroll
|   |   |   +-- New hires, terminations, comp changes, org changes
|   |   |   +-- Frequency: real-time (event) or daily batch
|   |   |   +-- Use: ADP Pay Data Input / Paylocity Employee API / Paychex Worker API
|   |   +-- Pay data input needed: Time --> Payroll
|   |   |   +-- Hours, earnings, deductions, reimbursements
|   |   |   +-- Use: ADP Pay Data Input v1 / Paylocity Additional Rates / Paychex Check API
|   |   +-- Go to PAYROLL RESULTS section
|   +-- Global payroll aggregator (CloudPay, Papaya, Deel)
|       +-- Single API for multi-country: aggregator handles in-country providers
|       +-- Employee master sync: HRIS --> Aggregator API
|       +-- Payroll results: Aggregator --> GL posting
|       +-- Go to GL POSTING section
|
+-- PAYROLL RESULTS: How do you extract payroll output?
|   +-- ADP: Payroll Output API (pay statements, check-level detail)
|   +-- Paylocity: PayStatements API (summary + detail)
|   +-- Paychex: Check endpoint (pay component breakdown)
|   +-- All providers: also offer flat file export (CSV/custom) as fallback
|
+-- GL POSTING: How does payroll data reach the ERP?
|   +-- Option A: Summarized journal entry (most common)
|   |   +-- One journal entry per payroll run per legal entity
|   |   +-- Debit: Salary Expense, Tax Expense, Benefits Expense (by dept/CC)
|   |   +-- Credit: Cash, Tax Payable, Benefits Payable, Garnishment Payable
|   |   +-- Format: ERP journal API or GL import file
|   +-- Option B: Detailed journal entry
|   |   +-- One line per employee per earning/deduction code
|   |   +-- Pros: full auditability, drill-down to employee
|   |   +-- Cons: massive volume (1,000 employees x 20 codes = 20,000 lines)
|   +-- Option C: File-based GL import
|       +-- CSV/XML file with journal entry lines
|       +-- Uploaded to ERP via SFTP, file import, or batch API
|       +-- Common for: SAP (IDoc), Oracle (FBDI), D365 (data entities)
|
+-- BANK FILE: How do payments get to employees?
    +-- US: ACH via NACHA file format
    +-- UK: BACS (3-day cycle)
    +-- EU: SEPA Credit Transfer (SCT) XML
    +-- Other: country-specific formats
    +-- Most payroll providers handle bank files directly
    +-- HRIS-to-bank only needed for native payroll without bank integration
```

## Quick Reference

### Integration Flow: HRIS --> Payroll --> ERP GL

| Step | Source | Action | Target | Data Objects | Timing | Failure Handling |
|---|---|---|---|---|---|---|
| 1 | HRIS (Workday/SF) | New hire / change event triggers sync | Payroll Provider | Employee master: name, SSN, tax elections, direct deposit, org assignment | Real-time or daily batch | Retry 3x, alert payroll admin, hold employee from payroll run |
| 2 | Time & Attendance | Hours, OT, PTO used exported | Payroll Provider | Timecard data: regular hours, OT hours, PTO hours by earnings code | Pre-payroll (1-2 days before run) | Manual entry fallback, deadline alert |
| 3 | Payroll Provider | Payroll calculation engine runs | Internal | Gross-to-net: earnings, taxes (fed/state/local), deductions, garnishments, employer taxes | Payroll run schedule (bi-weekly, semi-monthly) | Re-run with corrections, void/reverse checks |
| 4 | Payroll Provider | Payroll results extracted via API | iPaaS / Middleware | Pay statements, GL summary, tax deposits, check register | Post-run (within hours) | Retry API call, fall back to file export |
| 5 | iPaaS / Middleware | Transform payroll output to GL journal format | Target ERP | Journal entry: account, amount, debit/credit, entity, cost center, period | Post-run (same day) | DLQ + manual journal entry creation |
| 6 | iPaaS / Middleware | Post journal entry to ERP GL | Target ERP | GL journal entry (summarized or detailed) | Before period close | Retry, validation error notification, manual posting |
| 7 | Payroll Provider | Generate bank payment file | Bank / Payment Processor | ACH/BACS/SEPA payment file | Before bank cutoff | Re-generate, manual wire as last resort |
| 8 | Payroll Provider | File tax returns/deposits | Tax Authorities | 941, SUI, state withholding, W-2/1099 | Per statutory deadline | Manual filing, extension request |

## Step-by-Step Integration Guide

### 1. Set up employee master data sync (HRIS --> Payroll)

Configure the HRIS to push new hires, terminations, compensation changes, and organizational reassignments to the payroll provider. For ADP, use the Worker v2 API to create/update employee records. For Paylocity, use the Employee (v2) endpoint. For native payroll (Workday, SAP ECP), this sync is automatic. [src1, src2, src3]

```python
# Example: Sync new hire from Workday to ADP via iPaaS
# Input:  Workday new hire event (worker_id, hire_date, compensation, tax_elections)
# Output: ADP worker record created with payroll-ready status

import requests

# Step 1: Get Workday employee data via RaaS
workday_report_url = "https://{tenant}.workday.com/ccx/service/customreport2/{tenant}/{report_owner}/{report_name}?format=json"
workday_headers = {"Authorization": f"Bearer {workday_token}"}
employee_data = requests.get(workday_report_url, headers=workday_headers).json()

# Step 2: Transform to ADP format
adp_payload = {
    "events": [{
        "data": {
            "transform": {
                "worker": {
                    "person": {
                        "legalName": {
                            "givenName": employee_data["First_Name"],
                            "familyName1": employee_data["Last_Name"]
                        }
                    },
                    "workerDates": {
                        "originalHireDate": employee_data["Hire_Date"]  # ISO 8601
                    }
                }
            }
        }
    }]
}

# Step 3: Create worker in ADP
adp_url = "https://api.adp.com/hr/v2/workers"
adp_headers = {
    "Authorization": f"Bearer {adp_token}",
    "Content-Type": "application/json"
}
response = requests.post(adp_url, json=adp_payload, headers=adp_headers)
# 201 Created = success
```

**Verify**: Check ADP worker record via `GET /hr/v2/workers/{aoid}` -- expected: worker status = "Active", hire date matches source.

### 2. Configure earnings and deduction code mapping

Map HRIS compensation elements to payroll provider earnings/deduction codes. This mapping drives both payroll calculation accuracy and downstream GL posting. Pull available codes from the payroll provider API and build a cross-reference table. [src2, src3]

```python
# Example: Pull Paylocity earnings codes and build mapping
# Input:  Paylocity company ID
# Output: Dict of earnings codes available for mapping

import requests

company_id = "YOUR_COMPANY_ID"
url = f"https://api.paylocity.com/api/v2/companies/{company_id}/earnings"
headers = {
    "Authorization": f"Bearer {paylocity_token}",
    "Content-Type": "application/json"
}

response = requests.get(url, headers=headers)
earnings_codes = response.json()

# Build mapping: HRIS earnings type --> Paylocity code
EARNINGS_MAP = {
    "Regular":     {"code": "REG",  "category": "E", "gl_account": "6100-100"},
    "Overtime":    {"code": "OT",   "category": "E", "gl_account": "6100-110"},
    "Bonus":       {"code": "BON",  "category": "E", "gl_account": "6100-200"},
    "Commission":  {"code": "COMM", "category": "E", "gl_account": "6100-300"},
    "PTO":         {"code": "PTO",  "category": "E", "gl_account": "6100-400"},
    "401k_EE":     {"code": "401K", "category": "D", "gl_account": "2300-100"},
    "Medical_EE":  {"code": "MED",  "category": "D", "gl_account": "2300-200"},
    "FedTax":      {"code": "FIT",  "category": "T", "gl_account": "2200-100"},
    "StateTax":    {"code": "SIT",  "category": "T", "gl_account": "2200-200"},
    "FICA_SS_EE":  {"code": "SS",   "category": "T", "gl_account": "2200-300"},
    "FICA_Med_EE": {"code": "MED",  "category": "T", "gl_account": "2200-400"},
}
```

**Verify**: For each HRIS earnings type, confirm a valid payroll code exists and GL account is mapped. Run a reconciliation report after first payroll.

### 3. Extract payroll results post-run

After each payroll run completes, extract the results via API to build GL journal entries. Use the provider's pay statement or payroll summary endpoint. [src2, src3, src5]

```python
# Example: Extract ADP payroll results for GL posting
# Input:  ADP company code, pay period end date
# Output: Payroll summary by earnings code, tax type, deduction type

import requests

# ADP Payroll Output -- pay statements for a pay period
url = f"https://api.adp.com/payroll/v1/payroll-output"
headers = {"Authorization": f"Bearer {adp_token}"}
params = {
    "payPeriod.endDate": "2026-02-28",
    "payPeriod.payFrequency.codeValue": "B"  # Bi-weekly
}
response = requests.get(url, headers=headers, params=params)
payroll_data = response.json()

# Aggregate by GL account for journal entry
gl_entries = {}
for statement in payroll_data.get("payStatements", []):
    for earning in statement.get("earnings", []):
        code = earning["earningCode"]["codeValue"]
        amount = float(earning["amount"]["amountValue"])
        gl_account = EARNINGS_MAP.get(code, {}).get("gl_account", "6100-999")
        cost_center = statement.get("costCenter", "0000")
        key = (gl_account, cost_center, "D")  # Debit for expenses
        gl_entries[key] = gl_entries.get(key, 0) + amount
```

**Verify**: Total debits = total credits in the generated journal entry. Net pay + taxes + deductions = gross pay.

### 4. Build and post GL journal entries to ERP

Transform aggregated payroll data into the ERP's journal entry format and post. Each ERP has a different API for journal creation. [src6, src7]

```python
# Example: Post payroll journal to SAP S/4HANA via OData
# Input:  Aggregated GL entries from step 3
# Output: Posted journal entry document number

import requests

s4_url = "https://{host}/sap/opu/odata/sap/API_JOURNALENTRYITEMBASIC_SRV/JournalEntryItemBasic"
headers = {
    "Authorization": f"Bearer {s4_token}",
    "Content-Type": "application/json",
    "X-CSRF-Token": csrf_token  # Fetch first via GET with X-CSRF-Token: Fetch
}

# Build journal entry payload
journal_items = []
for (gl_account, cost_center, dc_indicator), amount in gl_entries.items():
    journal_items.append({
        "CompanyCode": "1000",
        "GLAccount": gl_account.replace("-", ""),
        "CostCenter": cost_center,
        "AmountInTransactionCurrency": str(amount),
        "TransactionCurrency": "USD",
        "DebitCreditCode": dc_indicator,  # "D" or "C"
        "DocumentDate": "2026-02-28",
        "PostingDate": "2026-02-28",
        "DocumentHeaderText": "Payroll Feb 2026 P2"
    })

payload = {"JournalEntryItemBasic": journal_items}
response = requests.post(s4_url, json=payload, headers=headers)
# 201 = document posted; capture AccountingDocument number
doc_number = response.json().get("d", {}).get("AccountingDocument")
```

**Verify**: `GET /JournalEntryItemBasic?$filter=AccountingDocument eq '{doc_number}'` returns all posted lines. Debit total = Credit total.

### 5. Validate and reconcile

Run a 3-way reconciliation: (1) payroll provider gross-to-net report, (2) GL journal entry totals, (3) bank file net pay total. Discrepancies indicate mapping errors or timing differences. [src7]

```python
# Reconciliation check
payroll_gross = sum(s["grossPay"] for s in payroll_data["payStatements"])
payroll_net = sum(s["netPay"] for s in payroll_data["payStatements"])
payroll_taxes = sum(s["totalTaxes"] for s in payroll_data["payStatements"])
payroll_deductions = sum(s["totalDeductions"] for s in payroll_data["payStatements"])

gl_total_debits = sum(v for (_, _, dc), v in gl_entries.items() if dc == "D")
gl_total_credits = sum(v for (_, _, dc), v in gl_entries.items() if dc == "C")
bank_file_total = sum(line["amount"] for line in ach_file_lines)

assert abs(gl_total_debits - gl_total_credits) < 0.01, "GL out of balance"
assert abs(payroll_net - bank_file_total) < 0.01, "Bank file mismatch"
assert abs(payroll_gross - (payroll_net + payroll_taxes + payroll_deductions)) < 0.01, "Gross-to-net mismatch"
```

**Verify**: All three assertions pass. If any fail, investigate the specific discrepancy category (earnings, taxes, or deductions).

## Data Mapping

### Field Mapping Reference: HRIS --> Payroll Provider

| Source Field (HRIS) | Target Field (Payroll) | Type | Transform | Gotcha |
|---|---|---|---|---|
| Employee ID | External ID / Badge Number | String | Direct | ADP uses associateOID (AOID) internally -- map your ID to their external ref |
| Legal First Name | First Name / Given Name | String | Direct | Paylocity has 40-char limit; Workday allows 100 |
| SSN / National ID | SSN / Tax ID | Encrypted String | Encrypt in transit | Different masking rules per provider; never log |
| Hire Date | Original Hire Date | Date (ISO 8601) | Direct | Rehire: some providers need both original and rehire date |
| Annual Salary | Compensation Rate | Decimal | Convert to pay-period amount | Must match pay frequency: annual/26 (bi-weekly) or annual/24 (semi-monthly) |
| Cost Center | Department Code | String | Map via cross-reference table | HRIS cost center IDs rarely match payroll dept codes |
| Work Location (State) | Tax Jurisdiction | String/Code | Map state to FIPS/jurisdiction code | Remote workers: may need resident + work state |
| Federal Filing Status | W-4 Filing Status | Code | Map to provider-specific code | Post-2020 W-4 uses different fields than legacy |
| Direct Deposit | Bank Account + Routing | Encrypted | Provider-specific format | ADP: separate account endpoint; Paylocity: within employee record |

### Payroll Output --> GL Account Mapping

| Payroll Component | GL Account (Typical) | Debit/Credit | Category | Notes |
|---|---|---|---|---|
| Regular Wages | 6100 Salary Expense | Debit | Earnings | Split by department/cost center |
| Overtime | 6110 Overtime Expense | Debit | Earnings | Must track FLSA compliance |
| Bonus | 6120 Bonus Expense | Debit | Earnings | May post to different period than paid |
| Employer FICA (SS + Medicare) | 6200 Payroll Tax Expense | Debit | Employer Tax | 7.65% of gross up to SS wage base |
| Employer FUTA | 6210 FUTA Expense | Debit | Employer Tax | 0.6% on first $7,000 per employee |
| Employer SUI | 6220 SUI Expense | Debit | Employer Tax | Rate varies by state and experience |
| Employer Health Insurance | 6300 Benefits Expense | Debit | Employer Benefit | Employer portion only |
| Employer 401k Match | 6310 Retirement Expense | Debit | Employer Benefit | Match formula varies |
| Employee Federal Tax Withheld | 2200 Federal Tax Payable | Credit | Liability | Due to IRS per deposit schedule |
| Employee State Tax Withheld | 2210 State Tax Payable | Credit | Liability | Due per state schedule |
| Employee FICA Withheld | 2220 FICA Payable | Credit | Liability | Combined with employer portion for deposit |
| Employee 401k Deduction | 2300 401k Payable | Credit | Liability | Remit to plan administrator |
| Employee Health Premium | 2310 Health Insurance Payable | Credit | Liability | Employee portion pre-tax (Sec 125) |
| Net Pay | 1000 Cash / Payroll Bank Account | Credit | Asset | ACH/check amount to employees |

### Data Type Gotchas

- **Date formats**: ADP returns ISO 8601 dates; Paylocity returns MM/DD/YYYY in some endpoints and ISO in others. Always parse defensively. [src2, src3]
- **Amount precision**: Payroll amounts should carry 2 decimal places minimum. Some providers return amounts as strings, some as floats. Always use Decimal/BigNumber, never floating point arithmetic. [src7]
- **Tax jurisdiction codes**: ADP uses proprietary jurisdiction codes, Paylocity uses FIPS codes, Paychex uses their own mapping. Build a cross-reference table. [src2, src3, src5]
- **Multi-entity payroll runs**: Some providers batch all entities into one payroll output; others separate by legal entity. GL posting logic must handle both patterns. [src6]
- **Negative amounts**: Voided checks, retroactive adjustments, and garnishment reversals produce negative amounts. GL journal must correctly reverse the original posting. [src7]

## Error Handling & Failure Points

### Common Error Codes

| Code/Error | Provider | Meaning | Cause | Resolution |
|---|---|---|---|---|
| 429 Too Many Requests | All REST APIs | Rate limit exceeded | Too many API calls in window | Exponential backoff: wait 2^n seconds, max 5 retries |
| 401 Unauthorized | All | Token expired or invalid | OAuth token expiry, certificate rotation | Refresh token; check ISU/API user status |
| INVALID_FIELD_VALUE | ADP | Field validation failure | SSN format, date format, invalid code | Check field spec in ADP API docs; validate before send |
| DUPLICATE_RECORD | Paylocity | Employee already exists | Duplicate SSN or employee ID | Check existing records first; use upsert pattern |
| POSTING_PERIOD_CLOSED | SAP S/4HANA | GL period already closed | Journal posted after period close | Open special period or post to next period with accrual |
| UNBALANCED_ENTRY | ERP (all) | Debits != Credits | Rounding errors or missing lines | Add rounding adjustment line; re-validate mappings |
| INVALID_COST_CENTER | ERP (all) | Cost center not in master data | New dept not yet created in ERP | Sync org structure before payroll GL posting |
| BANK_REJECT_R03 | ACH/NACHA | No account / unable to locate | Invalid routing/account number | Contact employee, update direct deposit info |

### Failure Points in Production

- **Earnings code mapping drift**: Payroll provider adds new earnings codes (e.g., new state-specific sick leave) without notifying integration team. GL posting maps to "Misc Expense" catch-all. Fix: `Monitor for unmapped codes every payroll run; alert on any GL posting to catch-all account`. [src6]
- **Multi-entity GL split failure**: Employee transfers between legal entities mid-pay-period. Payroll output shows split pay, but GL mapping only expects one entity. Fix: `Build entity split logic that handles partial-period allocation based on transfer effective date`. [src7]
- **Retroactive pay adjustment double-posting**: Retro pay calculated in current period but GL mapping posts to both current and prior period without reversing the original. Fix: `Implement reversal-and-repost pattern: reverse original journal entry, post corrected entry for prior period, post current period adjustment`. [src7]
- **Bank file timing failure**: ACH file generated but not submitted before bank cutoff (2 PM ET). Employees miss direct deposit. Fix: `Automate file submission immediately after generation; build 1-hour buffer before cutoff; have manual wire process as fallback`. [src8]
- **Tax jurisdiction mismatch**: Remote worker's tax withholding calculated for work state but employee resides in a state with reciprocity agreement. Over/under withholding. Fix: `Maintain work-state + resident-state mapping per employee; update when employees relocate; most payroll providers handle this if both states are configured`. [src8]
- **OAuth token expiry during payroll run**: Long-running payroll extraction spans past token lifetime. API calls fail midway. Fix: `Implement token refresh logic with 5-minute pre-expiry buffer; checkpoint extraction progress for restart`. [src2]

## Anti-Patterns

### Wrong: Posting one GL journal entry per employee per pay component

```python
# BAD -- creates thousands of GL lines, overwhelms ERP, makes reconciliation a nightmare
for employee in payroll_results:
    for component in employee["pay_components"]:
        post_journal_entry(
            account=component["gl_account"],
            amount=component["amount"],
            reference=f"Payroll-{employee['id']}-{component['code']}"
        )
# 1,000 employees x 15 components = 15,000 individual journal entries
```

### Correct: Summarize by GL account + cost center, post one journal entry per payroll run per entity

```python
# GOOD -- aggregates to manageable journal entry size, standard practice
journal_lines = {}
for employee in payroll_results:
    for component in employee["pay_components"]:
        key = (component["gl_account"], employee["cost_center"],
               component["debit_credit"], employee["legal_entity"])
        journal_lines[key] = journal_lines.get(key, 0) + component["amount"]

for entity in legal_entities:
    entity_lines = {k: v for k, v in journal_lines.items() if k[3] == entity}
    post_single_journal_entry(entity, entity_lines)
# 1 journal entry per entity, typically 30-80 lines
```

### Wrong: Hardcoding earnings/deduction code mappings

```python
# BAD -- breaks when payroll provider adds/changes codes
def map_earning(code):
    if code == "REG": return "6100"
    if code == "OT": return "6110"
    # New state sick leave code added by provider -- not handled
    # Falls through with no mapping, data lost
```

### Correct: Use configurable mapping table with fallback alerting

```python
# GOOD -- external config, unknown-code alerting, audit trail
import json

with open("earnings_gl_map.json") as f:
    EARNINGS_MAP = json.load(f)  # Maintained by payroll team, version-controlled

def map_earning(code, cost_center):
    mapping = EARNINGS_MAP.get(code)
    if not mapping:
        alert_payroll_team(f"Unmapped earnings code: {code}")
        return {"gl_account": "6199-000", "description": f"UNMAPPED-{code}"}  # Catch-all with alert
    return mapping
```

### Wrong: Running payroll GL posting without pre-validation

```python
# BAD -- posts invalid journal, requires manual reversal in ERP
journal = build_journal_from_payroll(payroll_data)
erp_client.post_journal(journal)  # May fail with unbalanced entry, invalid accounts, etc.
```

### Correct: Validate journal entry before posting, with dry-run option

```python
# GOOD -- catches errors before they hit the ERP
journal = build_journal_from_payroll(payroll_data)

# Pre-validation checks
errors = []
total_debits = sum(line["amount"] for line in journal if line["dc"] == "D")
total_credits = sum(line["amount"] for line in journal if line["dc"] == "C")
if abs(total_debits - total_credits) > 0.01:
    errors.append(f"Unbalanced: debits={total_debits}, credits={total_credits}")

for line in journal:
    if not erp_client.validate_account(line["gl_account"]):
        errors.append(f"Invalid GL account: {line['gl_account']}")
    if not erp_client.validate_cost_center(line["cost_center"]):
        errors.append(f"Invalid cost center: {line['cost_center']}")

if errors:
    alert_finance_team(errors)
    log_to_dlq(journal, errors)
else:
    erp_client.post_journal(journal)
```

## Common Pitfalls

- **Pay frequency mismatch**: HRIS stores annual salary but payroll provider expects per-period amount. If pay frequency changes (bi-weekly to semi-monthly), the divisor changes from 26 to 24. Fix: `Always derive per-period amount at sync time using the payroll provider's pay frequency code; never store and forward a cached per-period amount`. [src6]
- **Ignoring payroll provider sandbox limitations**: ADP and Paylocity sandboxes have limited data and different behavior from production. Test integrations with production-volume data in sandbox before go-live. Fix: `Request a full sandbox refresh from provider; use synthetic data generators to test edge cases (multi-state, high earners, garnishments)`. [src2, src3]
- **GL posting to wrong fiscal period**: Payroll runs on Jan 31 but check date is Feb 1. GL posts to January but should post to February (check-date accounting). Fix: `Use check date (pay date), not payroll run date, as the GL posting date. Confirm your organization's payroll accounting policy (accrual vs. cash basis)`. [src7]
- **Not handling supplemental payroll runs**: Off-cycle payroll (bonuses, termination pay, corrections) uses different tax calculation rules (supplemental flat rate vs. aggregate method). GL posting logic must handle both regular and supplemental runs. Fix: `Tag each payroll run with type (regular/supplemental/correction) and apply appropriate GL mapping rules`. [src7]
- **Missing employer tax liability postings**: Integration extracts employee-side taxes and deductions but omits employer-side FICA, FUTA, SUI, and benefit contributions. GL journal is incomplete. Fix: `Always extract both employee and employer portions; employer taxes are a separate set of GL debit/credit lines`. [src7]
- **Certificate/credential rotation disrupts production**: Payroll runs on a strict schedule. If OAuth certificates expire or API keys are rotated without updating the integration, the payroll-to-GL pipeline breaks during a critical window. Fix: `Track certificate/key expiration dates; rotate 30 days before expiry; test with new credentials in sandbox first`. [src1, src4]

## Diagnostic Commands

```bash
# ADP: Test authentication and get worker list
curl -X POST "https://accounts.adp.com/auth/oauth/v2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_SECRET"
# Expected: {"access_token":"...","token_type":"Bearer","expires_in":3600}

curl -X GET "https://api.adp.com/hr/v2/workers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
# Expected: {"workers":[{"associateOID":"...","person":{...}}]}

# Paylocity: Get earnings codes for a company
curl -X GET "https://api.paylocity.com/api/v2/companies/{companyId}/earnings" \
  -H "Authorization: Bearer {token}"
# Expected: [{"earningsCode":"REG","description":"Regular Pay",...}]

# Paylocity: Get pay statements for GL extraction
curl -X GET "https://api.paylocity.com/api/v2/companies/{companyId}/employees/{employeeId}/paystatement/summary/{year}/{checkDate}" \
  -H "Authorization: Bearer {token}"
# Expected: {"grossPay":5000.00,"netPay":3500.00,"taxes":[...],"deductions":[...]}

# SAP S/4HANA: Verify GL account exists
curl -X GET "https://{host}/sap/opu/odata/sap/API_JOURNALENTRYITEMBASIC_SRV/A_GLAccountInChartOfAccounts('6100')" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
# Expected: {"d":{"GLAccount":"6100","GLAccountName":"Salary Expense",...}}

# Workday: Test RaaS report for payroll journal
curl -X GET "https://{tenant}.workday.com/ccx/service/customreport2/{tenant}/{owner}/Payroll_GL_Journal?format=json" \
  -H "Authorization: Bearer {token}"
# Expected: {"Report_Entry":[{"Journal_Line":...}]}
```

## Version History & Compatibility

| Provider / API | Current Version | Last Major Change | Status | Notes |
|---|---|---|---|---|
| ADP API v2 (Workers, Pay Data Input) | v2 | 2024-Q3 (Pay Data Input v2 beta) | GA | v1 still supported but deprecated for new integrations |
| Paylocity API | v2025-03-11 | 2025-Q1 (new endpoints, versioned URLs) | GA | Versioned API with dated releases |
| Paychex Flex API | v1 | 2024 (webhook support added) | GA | Sandbox available for all partners |
| Workday Web Services | 2025R2 (v43.x) | 2025-09 (REST API expansion) | GA | WWS versioned per Workday release; maintain minimum version |
| SAP SF EC OData | v4 | 2025 H2 (new compensation entities) | GA | Check SAP API Business Hub for entity availability |
| SAP ECP Integration | S/4HANA 2023+ | 2024 (cloud-native posting via web services) | GA | IDoc posting still supported; web service posting recommended |

### Deprecation Policy

ADP deprecates API versions with 12-month notice via their developer portal. Paylocity versions APIs by date (e.g., v2025-03-11) with backward compatibility for at least 12 months. Workday Web Services maintain backward compatibility within a release train but drop support for versions 3+ releases old. SAP follows a minimum 24-month deprecation notice for BTP/API changes. [src1, src2, src3, src4, src5]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Running HRIS + separate payroll provider and need automated GL posting | Using native payroll (Workday Payroll or SAP ECP) with same-vendor ERP | Vendor's built-in GL posting configuration (no middleware needed) |
| Multi-country payroll across 5+ countries with different providers | Single-country, single-provider setup with built-in GL export | Provider's native GL file export + ERP file import |
| Need real-time or same-day GL posting after each payroll run | Manual monthly journal entries are acceptable | Manual process with payroll register as source document |
| Managing 500+ employees with complex earnings/deduction structures | < 50 employees with simple payroll | Payroll provider's built-in QuickBooks/Xero sync |
| Compliance requires detailed audit trail of payroll-to-GL data flow | No regulatory audit requirements | Simpler file-based transfer |

## Cross-System Comparison

| Capability | Workday Payroll | SAP ECP | ADP Workforce Now | Paylocity | Paychex Flex |
|---|---|---|---|---|---|
| Native HRIS Integration | Built-in (Workday HCM) | Built-in (SF EC) | API required | API required | API required |
| GL Posting Method | Financial Accounting journal task | IDoc / web service to S/4HANA | API extract + external GL post | API extract + external GL post | API extract + external GL post |
| Multi-Country | 50+ countries native | 50+ countries native | Present in 140+ via partners | US + Canada | US only |
| API Style | SOAP (WWS) + REST | OData + RFC/IDoc | REST (JSON) | REST (JSON) | REST (JSON) |
| Rate Limit Model | Fair use (no hard limit) | Tenant-based | 30 req/min per endpoint | 120 req/min | Per agreement |
| Tax Filing | Included (managed by Workday) | Included (managed by SAP) | Included (ADP files) | Included (Paylocity files) | Included (Paychex files) |
| Bank File Generation | Included | Included | Included | Included | Included |
| Earnings Code Flexibility | Highly configurable | Highly configurable (wage types) | Configurable per company | Configurable per company | Configurable per company |
| Sandbox for Testing | Preview tenant | Test instance | Sandbox available | Sandbox available | Sandbox available |
| Typical Customer Size | 1,000+ employees | 5,000+ employees | 50-50,000 employees | 50-5,000 employees | 10-1,000 employees |

## Important Caveats

- **Multi-country payroll has no universal API**: Each payroll provider covers different countries with different API surfaces. Global payroll aggregators (CloudPay, Papaya Global, Deel) provide a single API but abstract away country-specific complexity at the cost of reduced control and visibility.
- **Payroll data is PII-heavy**: Every API call transmits SSN, salary, bank account details. All endpoints must use TLS 1.2+, tokens must never be logged, and data at rest must be encrypted. Compliance: SOC 2, GDPR Article 32, state privacy laws.
- **Earnings/deduction codes change**: Payroll providers add new codes for legislative changes (new state sick leave mandates, new tax categories). Integrations must handle unknown codes gracefully.
- **GL posting timing affects financial reporting**: Cash-basis vs. accrual-basis accounting determines whether you post on payroll run date or check date. Confirm with your finance team before building.
- **Rate limit values in this card are approximate**: Providers may adjust limits by contract tier or API version. Always verify against current API documentation and your specific service agreement.
- **Sandbox environments may not reflect production behavior**: Payroll calculation engines in sandbox often use simplified tax tables. Never validate tax accuracy in sandbox alone.

## Related Units

- [Workday HCM API Capabilities](/business/erp-integration/workday-hcm-api/2026)
- [SAP SuccessFactors Employee Central API](/business/erp-integration/successfactors-ec-api/2026)
- [ADP API Capabilities and Rate Limits](/business/erp-integration/adp-api-capabilities/2026)
- [Financial Period Close Automation](/business/erp-integration/period-close-automation/2026)
- [File-Based Payroll-to-GL Posting](/business/erp-integration/payroll-gl-file-based/2026)
