---
# === IDENTITY ===
id: business/erp-integration/hire-to-retire-integration/2026
canonical_question: "How do you implement Hire-to-Retire integration - HRIS to payroll to benefits to ERP?"
aliases:
  - "H2R integration architecture HRIS payroll benefits"
  - "How to connect HR system to payroll and benefits platforms"
  - "Employee lifecycle integration from hiring to separation"
  - "HRIS to ERP payroll benefits data flow"
entity_type: erp_integration
domain: business > erp-integration > hire-to-retire-integration
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Workday HCM"
    vendor: "Workday"
    version: "2025R2"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST, SOAP, RaaS"
  - name: "SAP SuccessFactors Employee Central"
    vendor: "SAP"
    version: "2H 2025"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "OData v2, SFAPI (SOAP)"
  - name: "Oracle HCM Cloud"
    vendor: "Oracle"
    version: "24D"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST, SOAP, FBDI"
  - name: "Microsoft Dynamics 365 Human Resources"
    vendor: "Microsoft"
    version: "10.0.40"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "OData v4, Dataverse API"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.82
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Workday 2025R1 deprecated SOAP-only Worker endpoints; SAP EC Payroll moved to SAP Integration Suite as mandatory middleware in 2H 2024"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Employee ID is the universal join key — if HRIS and payroll use different ID schemes, every integration breaks"
  - "Payroll cut-off dates create hard deadlines: data arriving after cut-off is rejected or deferred to next cycle"
  - "Benefits eligibility rules vary by jurisdiction — cannot assume US COBRA/ACA rules apply globally"
  - "Workday REST API: 60 requests/minute per resource type; SOAP: 1,000 requests/minute per tenant"
  - "SAP SuccessFactors OData: 200 concurrent connections per company instance; throttled beyond fair-use"
  - "Most payroll vendors require batch/file-based integration (SFTP/CSV) rather than real-time API for payroll input"
  - "PII data (SSN, bank details) must be encrypted in transit and at rest — payroll file transfers require SFTP with PGP or equivalent"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need single-system API capability reference (e.g., Workday REST API limits only)"
    use_instead: "business/erp-integration/workday-api-capabilities/2026"
  - condition: "Need SAP SuccessFactors-specific OData entity reference"
    use_instead: "business/erp-integration/sap-successfactors-api/2026"
  - condition: "Need payroll tax calculation rules, not integration architecture"
    use_instead: "finance/payroll-tax-compliance/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: hris_platform
    question: "Which HRIS/HCM platform is the system of record for employee data?"
    type: choice
    options:
      - "Workday HCM"
      - "SAP SuccessFactors Employee Central"
      - "Oracle HCM Cloud"
      - "Microsoft Dynamics 365 HR"
      - "ADP Workforce Now / Vantage"
      - "UKG Pro / Ready"
      - "BambooHR"
      - "Other"
  - key: payroll_provider
    question: "Which payroll system or provider processes payroll?"
    type: choice
    options:
      - "Native (same as HRIS — e.g., Workday Payroll, SAP EC Payroll)"
      - "ADP"
      - "Ceridian Dayforce"
      - "Paychex"
      - "Country-specific bureau"
      - "Other third-party"
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual records, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (webhook, CDC, platform events)"
      - "file-based (CSV/XML import/export)"
  - key: employee_count
    question: "How many employees are in scope?"
    type: choice
    options:
      - "< 500"
      - "500-5,000"
      - "5,000-50,000"
      - "> 50,000"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/hire-to-retire-integration/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/workday-api-capabilities/2026"
      label: "Workday API Capabilities — rate limits, auth flows, data access methods"
    - id: "business/erp-integration/sap-successfactors-api/2026"
      label: "SAP SuccessFactors API — OData v2, SFAPI, Employee Central entities"
  solves:
    - id: "business/erp-integration/onboarding-provisioning-playbook/2026"
      label: "Onboarding & Provisioning Playbook — HRIS-triggered identity and IT setup"
  alternative_to:
    - id: "business/erp-integration/payroll-integration-playbook/2026"
      label: "Payroll Integration Playbook — narrower scope, payroll-only focus"
  often_confused_with:
    - id: "business/erp-integration/identity-lifecycle-management/2026"
      label: "Identity Lifecycle Management — IT provisioning, not HR/payroll data flow"

# === SOURCES ===
sources:
  - id: src1
    title: "Introduction to the hire to retire end-to-end business process"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/guidance/business-processes/hire-to-retire-introduction
    type: official_docs
    published: 2025-05-08
    reliability: authoritative
  - id: src2
    title: "The Complete Guide to Building Workday HRIS API Integrations"
    author: Apideck
    url: https://www.apideck.com/blog/guide-to-build-workday-hris-api-integrations
    type: technical_blog
    published: 2025-06-15
    reliability: high
  - id: src3
    title: "Integrating Payroll and Benefits with Your HRIS: 3 Proven Approaches"
    author: OutSail
    url: https://www.outsail.co/post/integrating-payroll-and-benefits-with-your-hris-proven-approaches
    type: industry_report
    published: 2025-03-10
    reliability: moderate_high
  - id: src4
    title: "About Employee Central OData APIs"
    author: SAP
    url: https://help.sap.com/docs/successfactors-platform/sap-successfactors-api-reference-guide-odata-v2/about-employee-central-odata-apis
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src5
    title: "How ERP and HRIS Integration Drives Smarter, Stronger Organizations"
    author: CBIZ
    url: https://www.cbiz.com/insights/article/how-erp-and-hris-integration-drives-smarter-stronger-organizations
    type: industry_report
    published: 2025-04-22
    reliability: moderate_high
  - id: src6
    title: "SAP SuccessFactors Employee Central Integration with Workday"
    author: SAP
    url: https://api.sap.com/package/SAPSuccessFactorsEmployeeCentralIntegrationwithWorkday/overview
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src7
    title: "A Guide to HRIS Integrations: Best Practices, Use Cases, and Trends"
    author: Apideck
    url: https://www.apideck.com/blog/a-guide-to-hris-integrations-best-practices-use-cases-and-trends-for-2025
    type: technical_blog
    published: 2025-01-20
    reliability: high
---

# Hire-to-Retire Integration: HRIS to Payroll to Benefits to ERP

## TL;DR

- **Bottom line**: Hire-to-Retire (H2R) integration connects HRIS (system of record for employee data), payroll (compensation processing), benefits administration, and ERP (financial GL posting) through a hub-and-spoke architecture where the HRIS is the master hub and all downstream systems subscribe to employee lifecycle events.
- **Key limit**: Payroll cut-off dates create hard deadlines — any employee data change (new hire, termination, compensation change) that misses the payroll cut-off window is deferred to the next pay cycle, causing compliance and employee satisfaction issues.
- **Watch out for**: Employee ID mismatch between systems is the #1 integration failure — if HRIS uses a UUID and payroll uses a 6-digit numeric ID, you need a cross-reference table, not a transformation, because IDs must be stable and reversible.
- **Best for**: Organizations with 500+ employees running separate HRIS, payroll, and benefits platforms that need automated, auditable data flow across the employee lifecycle.
- **Authentication**: OAuth 2.0 (Workday, Oracle HCM), OAuth 2.0 + SAML (SAP SF), API keys or SFTP with PGP (payroll bureaus like ADP, Ceridian).

## System Profile

Hire-to-Retire integration is a cross-system integration playbook, not a single-system API reference. It orchestrates data flow across 4-6 systems covering the complete employee lifecycle: recruitment, onboarding, core HR, time and attendance, payroll processing, benefits administration, GL posting, and separation/offboarding. The HRIS/HCM platform serves as the system of record for employee master data, and all other systems are consumers.

This card covers the four most common enterprise HCM platforms (Workday, SAP SuccessFactors, Oracle HCM Cloud, Microsoft Dynamics 365 HR) and their integration patterns with third-party payroll providers, benefits platforms, and ERP financial systems. It does NOT cover single-vendor all-in-one deployments where payroll and benefits are native modules on the same platform (those are configuration guides, not integration guides). [src1]

| System | Role | API Surface | Direction |
|---|---|---|---|
| HRIS/HCM (Workday, SAP SF, Oracle HCM, D365 HR) | Employee master — source of truth for personal data, job, org, compensation | REST, SOAP, OData, RaaS | Outbound (publishes events) |
| Payroll Provider (ADP, Ceridian, native, bureau) | Compensation processing — gross-to-net, tax, deductions | SFTP/CSV, REST API, batch files | Inbound (consumes employee + time data) |
| Benefits Platform (PlanSource, bswift, EmployeeNavigator, native) | Enrollment, eligibility, carrier feeds | REST API, EDI 834, SFTP/CSV | Bidirectional (eligibility from HRIS, elections back to payroll) |
| ERP / Finance (SAP S/4HANA, Oracle ERP, NetSuite, D365 Finance) | GL posting — labor cost allocation, headcount reporting | OData, REST, BAPI/IDoc, journal import | Inbound (consumes payroll results) |
| Identity Provider (Okta, Azure AD/Entra, Ping) | Access provisioning — triggered by HRIS hire/term events | SCIM 2.0, REST | Inbound (consumes hire/term events) |
| Middleware / iPaaS (MuleSoft, Boomi, Workato, SAP Integration Suite) | Orchestration, transformation, error handling | N/A | Orchestrator |

## API Surfaces & Capabilities

| Platform | API Surface | Protocol | Best For | Rate Limit | Auth Method |
|---|---|---|---|---|---|
| Workday REST | HTTPS/JSON | Individual worker lookups, real-time events | 60 req/min per resource type | OAuth 2.0 |
| Workday SOAP | HTTPS/XML | Bulk worker data, legacy integrations | 1,000 req/min per tenant | ISU (Integration System User) |
| Workday RaaS | HTTPS/JSON,CSV,XML | Custom report extraction, batch data pulls | Shared with SOAP limit | ISU or OAuth 2.0 |
| SAP SF OData v2 | HTTPS/JSON,XML | Employee Central CRUD, real-time sync | 200 concurrent connections | OAuth 2.0 + SAML |
| SAP SF SFAPI (SOAP) | HTTPS/XML | Legacy bulk operations | Deprecated for new dev | Basic Auth |
| Oracle HCM REST | HTTPS/JSON | Worker, assignment, compensation data | Configurable per tenant | OAuth 2.0 |
| Oracle HCM FBDI | File-based (CSV) | Bulk data import (payroll, journal) | N/A (file upload) | OAuth 2.0 |
| D365 HR Dataverse | OData v4 / HTTPS | Employee entity CRUD, Power Platform integration | 6,000 req/5min per user | OAuth 2.0 (Azure AD) |
| ADP API | HTTPS/JSON | Worker, payroll data, pay statements | Varies by product tier | OAuth 2.0 |
| Generic Payroll SFTP | SFTP/CSV | Batch payroll input files | N/A (file transfer) | SSH keys + PGP encryption |

[src2, src4]

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Platform | Value | Notes |
|---|---|---|---|
| Max records per page | Workday REST | 100 | Use offset pagination |
| Max records per RaaS report | Workday RaaS | 2,000 per batch | Paginate with page parameter |
| Max records per OData query | SAP SuccessFactors | 1,000 | Use $skiptoken for pagination |
| Max FBDI file size | Oracle HCM | 250 MB | Split larger payroll files |
| Max Dataverse batch size | D365 HR | 1,000 operations | Use $batch endpoint |
| Max payload size | Workday REST | 5 MB | For worker create/update operations |

[src2, src4]

### Rolling / Daily Limits

| Limit Type | Platform | Value | Window |
|---|---|---|---|
| REST API calls | Workday | 60/min per resource | Per-minute rolling |
| SOAP API calls | Workday | 1,000/min per tenant | Per-minute rolling |
| Concurrent connections | SAP SuccessFactors | 200 per company instance | Concurrent |
| API calls | D365 HR (Dataverse) | 6,000 per 5 min per user | 5-min sliding window |
| Concurrent long-running | Oracle HCM | 25 per pod | Concurrent |
| SFTP sessions | Most payroll providers | 1-5 concurrent | Per account |

[src2, src4]

## Authentication

| Platform | Flow | Use When | Token Lifetime | Refresh? |
|---|---|---|---|---|
| Workday | OAuth 2.0 (Authorization Code) | User-context integrations | 1 hour | Yes — refresh token |
| Workday | ISU (Integration System User) | Server-to-server batch, RaaS | Session-based | New credentials per request |
| SAP SuccessFactors | OAuth 2.0 + SAML assertion | API access to Employee Central | Configurable (default 1h) | Yes |
| SAP SuccessFactors | Basic Auth (SFAPI) | Legacy SOAP integrations only | Session-based | No |
| Oracle HCM | OAuth 2.0 (Client Credentials) | Server-to-server | 1 hour | Yes |
| D365 HR | OAuth 2.0 (Azure AD / Entra) | All Dataverse API access | 1 hour | Yes — refresh token |
| ADP | OAuth 2.0 (Client Credentials) | API access to ADP endpoints | 1 hour | Yes |
| Payroll SFTP | SSH key pair + PGP | Batch file transfer to payroll bureaus | N/A | N/A |

[src2, src4]

### Authentication Gotchas

- Workday OAuth tokens expire after 1 hour — batch jobs running longer than 2 hours fail midway. Implement token refresh logic before every batch page request, not just at job start. [src2]
- SAP SuccessFactors SAML assertions have clock-skew sensitivity — if the middleware server clock drifts >5 minutes from SAP's server, auth silently fails with a generic 401. Use NTP sync. [src4]
- ADP API credentials are environment-specific — sandbox tokens do NOT work in production, and the OAuth endpoints differ per ADP product (Workforce Now vs Vantage vs GlobalView). [src7]
- PGP keys for payroll SFTP must be rotated annually — most payroll bureaus will reject files encrypted with expired keys without any error message, just silent file rejection. [src3]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **Employee ID as universal join key**: Every system in the H2R chain must share a common employee identifier. If HRIS uses UUID and payroll uses a 6-digit numeric ID, you must maintain a persistent cross-reference table in the middleware — NOT a transformation function, because IDs must be bidirectionally resolvable. [src5]
- **Payroll cut-off deadlines**: Payroll systems have hard cut-off dates (typically 3-5 business days before pay date). Any employee data change (hire, termination, comp change, tax withholding) arriving after cut-off is rejected or deferred. Integrations must enforce cut-off awareness. [src3]
- **PII encryption in transit**: Social Security Numbers, bank account details, and salary data MUST be encrypted in transit (TLS 1.2+ for API, PGP + SFTP for file-based). Many payroll providers mandate PGP encryption on flat files. [src3]
- **Benefits eligibility is jurisdiction-dependent**: US ACA/COBRA rules, EU GDPR constraints on health data, and country-specific statutory benefits mean eligibility logic cannot be hardcoded — it must be configurable per jurisdiction. [src1]
- **Retroactive changes break downstream**: A retroactive hire date or comp change in the HRIS forces payroll recalculation for all affected pay periods. Most payroll systems limit retro processing to 1-3 prior periods. [src3]
- **Native payroll =/= external payroll integration**: Workday Payroll, SAP EC Payroll, and Oracle Cloud Payroll are deeply embedded — their integration is configuration, not API. This card covers external payroll provider integration. [src6]
- **Concurrent connection limits on HRIS APIs can cause payroll-day bottlenecks**: When multiple downstream systems (payroll, benefits, identity, ERP) all pull employee data simultaneously around payroll processing, API rate limits become the binding constraint. Stagger integration schedules. [src2]

## Integration Pattern Decision Tree

```
START — Organization needs Hire-to-Retire integration
├── Is payroll native to the HRIS platform?
│   ├── YES (e.g., Workday Payroll in Workday HCM)
│   │   └── STOP — this is configuration, not integration. Consult vendor admin guide.
│   └── NO (separate payroll provider)
│       └── Continue ↓
├── What type of payroll provider?
│   ├── Cloud payroll with REST API (ADP WFN, Ceridian Dayforce)
│   │   ├── Use event-driven: HRIS publishes employee change events → iPaaS transforms → payroll API ingests
│   │   └── Supplement with scheduled batch for payroll input files (time data, deductions)
│   ├── Payroll bureau (no API, file-based only)
│   │   ├── Use batch/file: HRIS → iPaaS extracts → CSV/fixed-width → PGP encrypt → SFTP upload
│   │   └── Payroll results: bureau SFTP → iPaaS → parse → post to ERP GL
│   └── On-premise payroll (SAP, Oracle)
│       └── Use middleware: SAP Integration Suite / Oracle OIC / MuleSoft → IDoc/BAPI or FBDI
├── Benefits integration pattern?
│   ├── Native benefits in HRIS → configuration only
│   ├── Third-party benefits platform (PlanSource, bswift)
│   │   ├── Eligibility events: HRIS → iPaaS → benefits platform API (real-time preferred)
│   │   ├── Enrollment elections: benefits platform → iPaaS → payroll deductions (batch, pre-cut-off)
│   │   └── Carrier feeds: benefits platform → EDI 834 → carriers (separate integration)
│   └── Broker-managed → typically file-based (CSV exchange via SFTP)
├── ERP / Finance GL posting?
│   ├── Payroll results → iPaaS → journal entry → ERP GL (batch, post-payroll-run)
│   ├── Labor cost allocation: map cost centers from HRIS org structure → ERP chart of accounts
│   └── Headcount reporting: HRIS → data warehouse or ERP planning module (scheduled extract)
└── Identity / IT provisioning?
    ├── HRIS hire event → SCIM 2.0 → IdP (Okta, Azure AD/Entra) → downstream app provisioning
    └── HRIS termination event → SCIM 2.0 → IdP → deprovisioning (time-critical: same-day)
```

## Quick Reference

| Step | Source System | Action | Target System | Data Objects | Timing | Failure Handling |
|---|---|---|---|---|---|---|
| 1 | Recruiter / ATS | Candidate accepted → create employee | HRIS | Employee master, job, comp | Real-time (event) | Retry 3x, manual review |
| 2 | HRIS | New hire event | Identity Provider (SCIM) | User account, role, email | Real-time (< 1 hour) | Alert IT helpdesk |
| 3 | HRIS | Eligibility event | Benefits Platform | Employee demographics, job, dependents | Real-time or daily batch | Queue for next batch |
| 4 | Benefits Platform | Enrollment elections | Payroll | Deduction codes, amounts, dates | Batch (pre-cut-off) | Hold; escalate if near cut-off |
| 5 | HRIS + Time System | Employee + time data | Payroll | Hours, earnings, deductions, tax | Batch (per pay period) | Block payroll run; alert HR |
| 6 | Payroll | Payroll results (gross-to-net) | ERP / Finance | Journal entries, cost allocations | Batch (post-payroll) | Retry; hold GL close |
| 7 | Payroll | Pay statements | Employee Self-Service | Pay stubs, tax forms | Post-payroll | Non-blocking; retry |
| 8 | HRIS | Termination event | All downstream systems | Separation date, final pay flag, benefits end date | Real-time + batch | Escalate: missed term = security risk |

## Step-by-Step Integration Guide

### 1. Establish Employee ID cross-reference

Map employee identifiers across all systems. The HRIS employee ID is the master key; create a persistent cross-reference (xref) table in your middleware. [src5]

```sql
-- Cross-reference table in middleware database
CREATE TABLE employee_xref (
    hris_employee_id    VARCHAR(50) PRIMARY KEY,
    payroll_employee_id VARCHAR(20) NOT NULL,
    benefits_member_id  VARCHAR(30),
    erp_person_id       VARCHAR(30),
    idp_user_id         VARCHAR(100),
    created_at          TIMESTAMP DEFAULT NOW(),
    updated_at          TIMESTAMP DEFAULT NOW()
);

-- Index for reverse lookups (payroll → HRIS)
CREATE INDEX idx_xref_payroll ON employee_xref(payroll_employee_id);
CREATE INDEX idx_xref_benefits ON employee_xref(benefits_member_id);
```

**Verify**: `SELECT COUNT(*) FROM employee_xref WHERE payroll_employee_id IS NULL;` → expected: `0` (every HRIS employee has a payroll mapping)

### 2. Configure HRIS outbound events (Workday example)

Set up Workday Business Process notifications to publish employee lifecycle events to your iPaaS. [src2]

```python
# Input:  Workday RaaS endpoint for new hires in last 24 hours
# Output: JSON array of new employee records

import requests
from datetime import datetime, timedelta

WORKDAY_TENANT = "your_tenant"
RAAS_ENDPOINT = f"https://wd5-services1.myworkday.com/{WORKDAY_TENANT}/customreport2/ISU_User/New_Hires_Report"

params = {
    "Effective_Date": (datetime.utcnow() - timedelta(days=1)).strftime("%Y-%m-%d"),
    "format": "json"
}

headers = {"Authorization": "Bearer <oauth_token>"}

response = requests.get(RAAS_ENDPOINT, params=params, headers=headers)
response.raise_for_status()

new_hires = response.json().get("Report_Entry", [])
for hire in new_hires:
    employee_id = hire["Employee_ID"]
    full_name = hire["Legal_Name"]
    hire_date = hire["Hire_Date"]
    job_profile = hire["Job_Profile"]
    cost_center = hire["Cost_Center"]
    print(f"New hire: {employee_id} - {full_name} starting {hire_date}")
```

**Verify**: `len(new_hires)` → expected: matches new hires in Workday for the date range

### 3. Transform and route to payroll

Transform HRIS employee data into payroll-provider format. Most payroll providers accept CSV via SFTP. [src3]

```python
# Input:  List of employee records from HRIS
# Output: CSV file formatted for payroll provider upload

import csv
import io
import paramiko
import gnupg

def transform_for_payroll(hris_records):
    """Transform HRIS employee records to payroll CSV format."""
    output = io.StringIO()
    writer = csv.writer(output)

    # Header row — payroll provider-specific
    writer.writerow([
        "EMP_ID", "LAST_NAME", "FIRST_NAME", "SSN_ENCRYPTED",
        "HIRE_DATE", "PAY_FREQUENCY", "SALARY_ANNUAL",
        "FED_FILING_STATUS", "STATE_CODE", "COST_CENTER",
        "DEPARTMENT_CODE", "EFFECTIVE_DATE"
    ])

    for emp in hris_records:
        writer.writerow([
            emp["payroll_id"],           # From xref table
            emp["last_name"],
            emp["first_name"],
            emp["ssn_encrypted"],        # Never transmit SSN in plaintext
            emp["hire_date"],            # Format: YYYY-MM-DD
            emp["pay_frequency"],        # W=Weekly, B=Biweekly, S=Semi-monthly, M=Monthly
            emp["annual_salary"],
            emp["fed_filing_status"],    # S=Single, M=Married, H=Head of Household
            emp["state_code"],           # 2-letter state code
            emp["cost_center"],
            emp["department_code"],
            emp["effective_date"]
        ])

    return output.getvalue()

def upload_to_payroll_sftp(csv_data, filename):
    """Encrypt with PGP and upload to payroll provider SFTP."""
    gpg = gnupg.GPG()
    encrypted = gpg.encrypt(csv_data, "payroll-provider@example.com", armor=True)

    transport = paramiko.Transport(("sftp.payrollprovider.com", 22))
    transport.connect(username="integration_user", pkey=paramiko.RSAKey.from_private_key_file("/path/to/key"))
    sftp = paramiko.SFTPClient.from_transport(transport)

    with sftp.open(f"/inbound/{filename}.pgp", "w") as f:
        f.write(str(encrypted))

    sftp.close()
    transport.close()
```

**Verify**: Check payroll provider portal → Inbound Files → expected: file listed with status "Received" or "Processing"

### 4. Sync benefits eligibility events

Push eligibility-triggering events (new hire, life event, termination) from HRIS to benefits platform in real time or daily batch. [src3]

```javascript
// Input:  HRIS employee change event (webhook payload)
// Output: Benefits platform enrollment eligibility trigger

const axios = require('axios');  // axios@1.6.x

async function syncBenefitsEligibility(hrisEvent) {
  const BENEFITS_API = 'https://api.benefitsplatform.com/v2/eligibility';

  const eligibilityPayload = {
    member_id: hrisEvent.benefits_member_id,  // From xref table
    event_type: mapEventType(hrisEvent.type), // new_hire, life_event, termination
    effective_date: hrisEvent.effective_date,
    employee: {
      first_name: hrisEvent.first_name,
      last_name: hrisEvent.last_name,
      date_of_birth: hrisEvent.dob,
      employment_status: hrisEvent.status,     // active, terminated, leave
      job_title: hrisEvent.job_title,
      hours_per_week: hrisEvent.scheduled_hours,
      salary: hrisEvent.annual_salary,
      hire_date: hrisEvent.hire_date,
      location_state: hrisEvent.work_state
    },
    dependents: hrisEvent.dependents || []
  };

  try {
    const response = await axios.post(BENEFITS_API, eligibilityPayload, {
      headers: {
        'Authorization': `Bearer ${process.env.BENEFITS_API_TOKEN}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000
    });
    console.log(`Benefits eligibility synced: ${response.data.enrollment_id}`);
    return response.data;
  } catch (error) {
    if (error.response?.status === 429) {
      // Rate limited — retry with backoff
      await new Promise(r => setTimeout(r, 5000));
      return syncBenefitsEligibility(hrisEvent);
    }
    throw error;
  }
}

function mapEventType(hrisType) {
  const mapping = {
    'Hire': 'new_hire',
    'Rehire': 'new_hire',
    'Life_Event': 'qualifying_life_event',
    'Termination': 'termination',
    'Leave_of_Absence': 'leave_start',
    'Return_from_Leave': 'leave_end'
  };
  return mapping[hrisType] || 'other';
}
```

**Verify**: Benefits platform → Employee → Enrollment Status → expected: "Open Enrollment" or "Eligible" status

### 5. Post payroll results to ERP General Ledger

After payroll runs, post journal entries to the ERP financial system for labor cost accounting. [src1]

```python
# Input:  Payroll results file (CSV from payroll provider)
# Output: Journal entries posted to ERP GL

import csv
from datetime import datetime

def parse_payroll_results(csv_path):
    """Parse payroll results and generate GL journal entries."""
    journal_entries = []

    with open(csv_path, 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            # Debit: Salary Expense (by cost center)
            journal_entries.append({
                "journal_date": row["pay_date"],
                "account": f"6100-{row['cost_center']}",  # Salary expense by cost center
                "debit": float(row["gross_pay"]),
                "credit": 0,
                "description": f"Payroll {row['pay_period']} - {row['cost_center']}",
                "reference": row["payroll_batch_id"]
            })
            # Credit: Payroll Liability
            journal_entries.append({
                "journal_date": row["pay_date"],
                "account": "2100",  # Payroll clearing/liability
                "debit": 0,
                "credit": float(row["net_pay"]),
                "description": f"Payroll {row['pay_period']} - Net pay",
                "reference": row["payroll_batch_id"]
            })
            # Credit: Tax withholdings
            journal_entries.append({
                "journal_date": row["pay_date"],
                "account": "2110",  # Tax withholding liability
                "debit": 0,
                "credit": float(row["total_tax"]),
                "description": f"Payroll {row['pay_period']} - Tax withholdings",
                "reference": row["payroll_batch_id"]
            })
            # Credit: Benefits deductions
            journal_entries.append({
                "journal_date": row["pay_date"],
                "account": "2120",  # Benefits deduction liability
                "debit": 0,
                "credit": float(row["total_deductions"]),
                "description": f"Payroll {row['pay_period']} - Benefits deductions",
                "reference": row["payroll_batch_id"]
            })

    return journal_entries

def post_to_erp_gl(journal_entries, erp_api_url, token):
    """Post journal entries to ERP via API."""
    import requests

    batch_payload = {
        "journal_batch": {
            "batch_name": f"PAYROLL_{datetime.utcnow().strftime('%Y%m%d')}",
            "entries": journal_entries
        }
    }

    response = requests.post(
        f"{erp_api_url}/api/v1/journals",
        json=batch_payload,
        headers={"Authorization": f"Bearer {token}"}
    )
    response.raise_for_status()
    return response.json()
```

**Verify**: ERP GL → Journal Entries → filter by batch name → expected: balanced debits = credits for pay period

## Data Mapping

### Field Mapping Reference

| Source Field (HRIS) | Target Field (Payroll) | Type | Transform | Gotcha |
|---|---|---|---|---|
| Employee_ID (UUID) | EMP_ID (numeric) | String→Int | Xref table lookup | Never derive — must be stable bidirectional mapping |
| Legal_Name.Last | LAST_NAME | String | Direct | Max 30 chars in most payroll systems vs 100+ in HRIS |
| Compensation.Annual_Salary | SALARY_ANNUAL | Decimal | Direct (if same currency) | Multi-currency: must convert at payroll-period exchange rate, not hire-date rate |
| Work_Address.State | STATE_CODE | String | Map to 2-letter code | HRIS may store full state name; payroll needs code |
| Pay_Frequency | PAY_FREQ_CODE | String | Map: Biweekly→B, Monthly→M | HRIS text vs payroll single-character code |
| Benefits.Medical_Plan | MEDICAL_DEDUCTION | Decimal | Look up deduction amount by plan code | Plan code ≠ deduction amount; must resolve from benefits platform elections |
| Tax_Elections.Federal_Filing | FED_STATUS | String | Map: Single→S, Married_Filing_Jointly→M | W-4 changes effective date matters: retroactive vs prospective |
| Hire_Date | ORIG_HIRE_DATE | Date | Direct | Rehires: distinguish original hire date from most recent hire date |
| Termination_Date | TERM_DATE | Date | Direct | Payroll needs this BEFORE last payroll run; late terminations cause overpayment |
| Cost_Center | COST_CENTER | String | Direct or xref | HRIS may use hierarchical codes (1000.2000.3000) while ERP uses flat codes (123456) |

[src3, src5]

### Data Type Gotchas

- **Date formats**: Workday returns ISO 8601 (YYYY-MM-DD); ADP expects MM/DD/YYYY; SAP uses YYYYMMDD. A single format mismatch silently corrupts hire dates and triggers wrong benefit eligibility windows. [src2]
- **Currency handling**: HRIS stores annual salary in employee's local currency; payroll may process in a different currency (e.g., global company with US payroll). Exchange rate source and timing (hire date vs pay date vs period-end) must be explicitly defined. [src3]
- **Address formats**: US-centric systems use State/ZIP; international addresses need Country + Province/Region + Postal Code. Payroll tax jurisdiction depends on work location address, not home address. [src7]
- **SSN/National ID**: Different systems mask differently (last 4 only, full encrypted, hashed). Integration must handle full SSN securely for payroll tax reporting while never logging it in plaintext. [src3]
- **Effective dating**: HRIS stores future-dated changes (promotion effective next Monday); payroll systems may not support future dating. Middleware must hold and release changes on the effective date. [src2]

## Error Handling & Failure Points

### Common Error Codes

| Code / Error | Platform | Meaning | Cause | Resolution |
|---|---|---|---|---|
| HTTP 429 | Workday REST | Rate limit exceeded | > 60 requests/min to same resource | Exponential backoff: wait 2^n seconds; check Retry-After header |
| HTTP 401 | All platforms | Authentication failure | Expired OAuth token or rotated credentials | Refresh token; check clock sync (SAML); verify environment-specific credentials |
| INVALID_EMPLOYEE_ID | Payroll | Employee not found in payroll system | Xref mapping missing or stale | Check xref table; verify employee was created in payroll before sending changes |
| CUTOFF_EXCEEDED | Payroll | Change received after payroll cut-off | Integration ran too late or retries pushed past deadline | Defer to next cycle; alert HR for manual adjustment if critical |
| DUPLICATE_RECORD | Benefits | Employee already enrolled | Race condition: event processed twice | Implement idempotency key on enrollment events |
| FILE_DECRYPT_FAIL | Payroll SFTP | PGP decryption failed | Expired PGP key or wrong recipient key | Rotate PGP keys; verify key fingerprint matches provider's current public key |
| GL_IMBALANCE | ERP | Journal entry debits != credits | Rounding errors in multi-currency conversion or missing deduction lines | Reconcile payroll totals before posting; add rounding adjustment line |

[src2, src3]

### Failure Points in Production

- **Payroll-day API rate limit collision**: When payroll processing triggers simultaneous requests from payroll, benefits, identity, and ERP systems against the HRIS API, rate limits cause cascading 429 errors. Fix: `stagger downstream system schedules by 30-60 minutes and implement dedicated integration users per system with separate rate limit pools`. [src2]
- **Retroactive compensation change breaks payroll history**: A manager backdates a promotion in the HRIS; the integration sends the retro change to payroll which tries to recalculate 3 months of pay periods. Fix: `enforce a retro change window limit (e.g., max 2 prior periods) in the middleware transformation layer and alert HR if the change exceeds the window`. [src3]
- **PGP key expiration on payroll SFTP**: PGP keys expire silently; the encrypted file is uploaded successfully but the payroll provider cannot decrypt it, and many providers offer no error notification. Fix: `monitor PGP key expiry dates with alerting 30 days before expiration; maintain backup key rotation process`. [src3]
- **Benefits open enrollment data volume spike**: During annual open enrollment, benefits data volume increases 10-50x. Integrations designed for daily trickle fail under burst load. Fix: `switch to bulk/batch mode during open enrollment windows; pre-scale iPaaS workers; run enrollment data in dedicated batch windows`. [src3]
- **Terminated employee still has active system access**: HRIS termination event fails to trigger identity deprovisioning due to middleware error. Fix: `implement a daily reconciliation job that compares HRIS active employees against IdP active users and flags mismatches for immediate review`. [src1]
- **Timezone mismatch on effective dates**: HRIS stores in UTC; payroll system uses local timezone. An employee terminated at 11 PM EST on March 31 appears as April 1 in UTC, causing one extra day of pay. Fix: `always convert to the payroll system's local timezone before transmitting effective dates`. [src2]

## Anti-Patterns

### Wrong: Point-to-point integration between every system pair

```
// BAD — N systems create N*(N-1)/2 integration points
// HRIS ↔ Payroll (custom API integration)
// HRIS ↔ Benefits (custom SFTP script)
// HRIS ↔ ERP (custom file import)
// Payroll ↔ ERP (custom journal posting)
// Benefits ↔ Payroll (custom deduction sync)
// Result: 10+ custom integrations, no central error handling, no audit trail
```

### Correct: Hub-and-spoke with iPaaS as orchestrator

```
// GOOD — iPaaS as central hub, N spoke connections
// HRIS → iPaaS (single outbound, event-driven)
//   iPaaS → Payroll (transform + route)
//   iPaaS → Benefits (transform + route)
//   iPaaS → ERP (transform + route)
//   iPaaS → IdP (SCIM provisioning)
// Result: N integrations, centralized logging, single error queue, audit trail
```

### Wrong: Real-time sync for payroll input data

```python
# BAD — pushing every compensation change to payroll in real time
def on_comp_change(event):
    payroll_api.update_salary(event.employee_id, event.new_salary)
    # Problem: 50 comp changes/day = 50 API calls
    # Payroll system recalculates after EACH one
    # Risk: partial updates if one fails mid-batch
```

### Correct: Batch payroll input with cut-off awareness

```python
# GOOD — collect all changes, send as single batch before cut-off
def prepare_payroll_batch(pay_period_end, cutoff_date):
    changes = hris_api.get_changes(
        since=last_batch_date,
        until=cutoff_date,
        types=["hire", "termination", "comp_change", "tax_change"]
    )
    # Validate all changes
    validated = [c for c in changes if validate_for_payroll(c)]
    # Send as single batch
    payroll_api.submit_batch(pay_period_end, validated)
    # Result: one atomic submission, all-or-nothing, easy to retry
```

### Wrong: Hardcoding benefits eligibility rules

```python
# BAD — hardcoding US-only ACA eligibility
def check_eligibility(employee):
    if employee.hours_per_week >= 30:  # ACA full-time threshold
        return "eligible"
    return "not_eligible"
    # Breaks for: part-time with state mandates, non-US employees,
    # variable-hour employees, union contracts
```

### Correct: Configurable eligibility with jurisdiction awareness

```python
# GOOD — jurisdiction-based eligibility engine
def check_eligibility(employee, jurisdiction_rules):
    rules = jurisdiction_rules.get(employee.work_country, {})
                              .get(employee.work_state, {})

    for rule in rules.get("medical_eligibility", []):
        if not rule.evaluate(employee):
            return {"eligible": False, "reason": rule.failure_reason}

    return {"eligible": True, "effective_date": calculate_effective_date(
        employee.hire_date, rules.get("waiting_period_days", 0)
    )}
```

## Common Pitfalls

- **Employee ID strategy not defined before integration build**: Teams start building integrations using "whatever ID the HRIS generates" and discover mid-project that the payroll system requires a different format. Fix: `Define the ID strategy and build the xref table as the FIRST integration task, before any data flows`. [src5]
- **Ignoring payroll provider file format versions**: ADP, Ceridian, and others periodically update their import file specs. Fix: `Version-pin your file format and subscribe to provider release notes; test format changes in sandbox before production cutover`. [src3]
- **Testing with synthetic data only**: Synthetic test data lacks the edge cases that break integrations: hyphenated last names, international addresses, employees with multiple active jobs, rehires with prior history. Fix: `Use anonymized production data snapshots for integration testing`. [src7]
- **Not handling partial batch failures**: A payroll batch of 5,000 employees fails on record #3,421 due to an invalid tax code. The remaining 1,579 records are never processed. Fix: `Implement record-level error handling — process valid records, quarantine invalid ones, alert HR for manual correction`. [src3]
- **Benefits carrier feed timing**: Benefits elections must reach insurance carriers before coverage start date. The chain (employee enrolls → benefits platform → iPaaS → carrier EDI 834 feed) has a 3-5 day latency. Fix: `Build carrier feed SLAs into the open enrollment calendar and test the end-to-end chain timing before go-live`. [src3]
- **Overlooking audit and compliance requirements**: SOX, GDPR, and HIPAA all have specific requirements for how employee and payroll data is transmitted, logged, and retained. Fix: `Build audit logging into every integration step: log what was sent, when, to whom, and hash the payload for tamper evidence — but never log PII in cleartext`. [src1]

## Diagnostic Commands

```bash
# Test Workday RaaS connectivity and auth
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $WD_TOKEN" \
  "https://wd5-services1.myworkday.com/$TENANT/customreport2/ISU_User/Test_Report?format=json"
# Expected: 200

# Verify SAP SuccessFactors OData Employee Central access
curl -s -X GET \
  "https://api.successfactors.com/odata/v2/User?\$top=1&\$format=json" \
  -H "Authorization: Bearer $SF_TOKEN"
# Expected: JSON with single user record

# Check payroll SFTP connectivity
sftp -o BatchMode=yes -i /path/to/key integration_user@sftp.payrollprovider.com <<< "ls /inbound/"
# Expected: directory listing of inbound folder

# Validate PGP key is not expired
gpg --list-keys payroll-provider@example.com | grep "expires:"
# Expected: expiry date in the future

# Count pending xref mappings (employees without payroll ID)
psql $MIDDLEWARE_DB -c "SELECT COUNT(*) FROM employee_xref WHERE payroll_employee_id IS NULL;"
# Expected: 0

# Verify payroll file was received (provider-specific API check)
curl -s -H "Authorization: Bearer $PAYROLL_TOKEN" \
  "https://api.payrollprovider.com/v1/files?status=received&date=$(date +%Y-%m-%d)"
# Expected: JSON listing uploaded file with status "received" or "processing"
```

## Version History & Compatibility

| Platform | Current Version | Previous Stable | Key Changes | Migration Notes |
|---|---|---|---|---|
| Workday | 2025R2 (current) | 2025R1 | SOAP-only Worker endpoints deprecated; REST Worker v2 GA | Migrate SOAP Worker integrations to REST; 12-month sunset |
| SAP SuccessFactors | 2H 2025 | 1H 2025 | SAP Integration Suite mandatory for EC Payroll; SFAPI deprecated | Use SAP BTP Integration Suite; no direct SFAPI for new integrations |
| Oracle HCM Cloud | 24D | 24C | New Hire event REST API enhancements; FBDI v3 | FBDI v2 supported until 25C; test v3 templates |
| D365 HR | 10.0.40 | 10.0.39 | Payroll integration API v2 with batch support | v1 still supported; v2 adds idempotency keys |
| ADP Workforce Now | 2025.3 | 2025.2 | New webhooks for worker lifecycle events | Webhooks in GA; replaces polling pattern |

[src1, src2, src4]

### Deprecation Policy

Workday announces deprecations in their biannual release notes with a minimum 12-month notice period. SAP SuccessFactors follows a 2-release deprecation cycle (approximately 12 months). Oracle HCM Cloud provides 3-release notice for API deprecations. ADP provides 6-month notice for API changes via developer portal. [src2, src4]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Running separate HRIS, payroll, and benefits systems that need automated data flow | Using a single all-in-one HCM suite (Workday + Workday Payroll) | Vendor's native configuration guide |
| 500+ employees where manual data entry creates compliance risk | < 50 employees with simple payroll | Manual process or spreadsheet-based onboarding |
| Multiple countries/jurisdictions with different payroll and benefits rules | Single country, single payroll provider, no benefits complexity | Simple HRIS-payroll file export |
| Need auditable, SOX-compliant data trail between HR and finance | Non-regulated industry with minimal audit requirements | Direct file transfer without middleware |
| Merging companies with different HR systems post-acquisition | Greenfield implementation where you can choose one platform | Single-vendor HCM selection (business/erp-selection category) |

## Cross-System Comparison

| Capability | Workday HCM | SAP SuccessFactors | Oracle HCM Cloud | D365 HR |
|---|---|---|---|---|
| Primary API | REST + RaaS | OData v2 | REST + FBDI | OData v4 (Dataverse) |
| Real-time events | Business Process notifications | Intelligent Services (event-driven) | Business Events | Dataverse webhooks |
| Payroll integration | Workday Payroll (native) or Payroll Connect API | EC Payroll (native) or Payroll Control Center | Cloud Payroll (native) or Global Payroll Interface | Payroll Integration API v2 |
| Benefits integration | Native Benefits module | Benefits Management module | Benefits module | Benefits management via Power Platform |
| iPaaS preference | Workday Studio, MuleSoft, Boomi | SAP Integration Suite (mandatory for EC Payroll), Boomi | Oracle Integration Cloud (OIC), MuleSoft | Power Automate, Azure Logic Apps |
| Rate limits | 60/min REST, 1K/min SOAP | 200 concurrent | Configurable per tenant | 6K per 5 min |
| Batch import | EIB (Enterprise Interface Builder) | Import/Export templates | FBDI (File-Based Data Import) | Data Management Framework |
| Auth model | OAuth 2.0, ISU | OAuth 2.0 + SAML | OAuth 2.0 | OAuth 2.0 (Azure AD) |
| Sandbox quality | Full copy available | Limited test instance | Full sandbox + preview | Sandbox environments |
| Global payroll partners | 75+ country payroll partners | 50+ via Payroll Control Center | 60+ via Global Payroll Interface | Via ISV partners |
| Change data capture | Workday Prism Analytics | EC Change API, Audit Log | Business Events + CDC | Dataverse Change Tracking |

[src1, src2, src4, src6]

## Important Caveats

- **Native payroll vs external payroll is a fundamentally different problem**: If both HRIS and payroll are from the same vendor (Workday HCM + Workday Payroll), the "integration" is configuration, not API. This card covers external/third-party payroll integration only. [src6]
- **Rate limits vary dramatically by vendor and edition**: Workday's 60 req/min REST limit is per-resource-type, not per-tenant — but SOAP's 1,000/min is per-tenant. Plan your architecture around the most restrictive limit. [src2]
- **Payroll provider API maturity varies enormously**: ADP and Ceridian have modern REST APIs; many country-specific payroll bureaus only accept CSV via SFTP. Design your middleware to support both patterns. [src3]
- **Benefits EDI 834 feeds are carrier-specific**: Each insurance carrier has quirks in their 834 implementation. The benefits platform handles this complexity, but expect 2-4 weeks of carrier testing per carrier during implementation. [src3]
- **This card covers integration architecture, not payroll tax rules, benefits plan design, or labor law compliance**: Those are domain-specific topics requiring jurisdiction-specific knowledge units. [src1]
- **API versions and rate limits change with each vendor release**: Always verify current limits against the vendor's latest release notes before designing integration capacity. Information in this card was verified as of March 2026. [src2, src4]

## Related Units

- [Workday API Capabilities](/business/erp-integration/workday-api-capabilities/2026) — detailed rate limits, auth flows, and data access methods for Workday
- [SAP SuccessFactors API](/business/erp-integration/sap-successfactors-api/2026) — OData v2 entity reference for Employee Central
- [Onboarding & Provisioning Playbook](/business/erp-integration/onboarding-provisioning-playbook/2026) — HRIS-triggered identity and IT provisioning
- [Payroll Integration Playbook](/business/erp-integration/payroll-integration-playbook/2026) — narrower scope focusing on payroll data flow only
