The feed said success and lost 812 people

The org structure is the least stable thing in SuccessFactors, and most employee data integrations are built directly on top of it. Position codes get renumbered and departments get folded into new legal entities. The feed that has run cleanly for two years starts describing a company that no longer exists, and the break is rarely loud.

I owned a nightly CompoundEmployee delta into ServiceNow and Active Directory for a manufacturer with about 11,000 employees. Over one weekend a reorg moved 2,140 people into two new company codes, and the transfer retired roughly 300 positions. For six nights the run finished with zero errors. For six nights, 812 workers were missing from both targets. We found out on day seven, when the service desk logged 31 tickets about approvals routing to managers who had already moved.

The cause was ordinary. The mapping step enriched each worker with position attributes by reading the Position entity as of the run date, and a retired position has an effectiveEndDate in the past, so that read came back empty. The mapping dropped those rows rather than failing them, and a dropped row counts as filtered, which does not fail an iFlow. The monitor reported accurately on what it measured. Nobody had asked it to measure whether every worker arrived.

Pick the key that survives a renumber

Start with the identifier the downstream systems will store, because that choice decides how much of a reorg you survive. If the target keeps a position code or a department code as its link back to HR, it is keeping something the HR team will renumber during the next restructuring. Those codes have a shelf life, and they get reused on new objects once the old ones retire.

Use personIdExternal as the anchor in every payload, and carry userId only for systems that already store it. In Employee Central, userId belongs to the employment rather than the person, so a worker with a global assignment ends up with more than one, and a rehire can produce a new one. The rules for picking identifiers that hold up under change are the ones covered in durable business keys. Employee data tests them harder than anything else in the estate.

The manager relationship needs the same discipline. EmpJob.managerId gives you a userId, so resolve it to personIdExternal before it leaves the boundary, and never send a manager name or email address as the linking value. Send the org unit's stable identifier alongside its display name, then have the target match on the identifier while showing the name. When that unit gets renamed on 1 July, nothing downstream has to be rebuilt.

Effective dating has to be a decision

SuccessFactors keeps time slices. A transfer typed in on 3 March with an effective date of 1 April exists in EmpJob the moment it is saved, carrying an effectiveStartDate in the future. Most target systems hold one current state and no history at all. Someone has to decide which slice the target receives, and when nobody decides, the middleware decides by accident.

Pick one of two behaviours and write it down. Either you read as of today with asOfDate and send only the state that is live right now, or you push future dated changes and store the effective date downstream. The second works only if the target can hold a dated change and apply it on the day. Mixing the two across different targets is how you get a week where the CRM believes someone has moved and the ticketing system does not.

The case that catches people is the cancelled future transfer. Someone enters a 1 April move, HR withdraws it on 20 March, and the row disappears from the next payload with no delta event to say a change was withdrawn. Any system you already notified keeps waiting for a move that will never happen. Write the answer into a small written integration contract, covering which date field drives the target and who reverses a change that never took place.

Leavers and rehires are where a reorg bites

During a reorg, terminations and hires stop meaning what your feed assumes they mean. Moving a worker between legal entities is often modelled as a termination in the old entity and a hire in the new one, sometimes on consecutive days, sometimes on the same day. A feed that turns every termination into an account disable will lock out two thousand people on Friday evening and build fresh accounts for them on Monday.

Before acting on a termination, check whether the person still has an active employment somewhere else. In CompoundEmployee, walk employmentNav for the same personIdExternal. In OData, read every EmpEmployment record for the person instead of the single employment the event pointed at. A rehire has to land on the record the first employment created, so the target needs a person record that outlives any single employment. Identity teams end up building the exception patterns for this anyway, and it goes better if HR integration agrees the rule with them first.

I now put a deliberate delay on deprovisioning. A termination gets staged, and the account is disabled only after the next morning's reconciliation confirms the person is inactive everywhere in the tenant. One extra day of access for a genuine leaver costs less than four hundred re-provisioning tickets and a payroll team that cannot log in. The same trade-off shows up in what breaks quietly between a hire and an exit.

Count both sides every morning

A success response tells you the call completed. It says nothing about whether the right number of people arrived, which is why every employee feed needs a count check that runs independently of it.

Mine runs at 06:15, after the nightly window closes. It reads the count of active workers per company code from Employee Central as of today, reads the same count from each target, writes both into one small table, and alerts when any company code differs by more than five workers or half a percent. It does not read the integration logs. If the feed is wrong, its own logs are wrong in the same direction.

Track the size of the delta as well. If the nightly run normally carries between 300 and 900 changed workers and last night it carried 41, that deserves a look before anyone reports a missing person. This counting habit is what makes reconciliation on a payroll cutover survivable, and there is no reason to save it for cutover weekend.

A test you can run this week

Two tests, both fit in an afternoon. First, pull 25 workers with an EmpJob row whose effectiveStartDate is in the future, then check what each target holds for them today. If any target already shows the future job title or the future manager, your query is reading the wrong slice and the downstream org chart is ahead of reality.

Second, in a test tenant, retire a position that currently holds an active worker, dating the retirement in the past. Run the nightly feed unchanged. If the worker does not arrive at the target and the run still reports success, you have the same defect I spent six days not noticing. Fix it by failing the row instead of dropping it, and by reading position attributes as of the worker's own effective date rather than the run date.

The harder problem is scheduling. A reorg lands on the HR calendar and the legal calendar months before it reaches anyone's integration plan. Ask in your next architecture review who is obliged to tell the integration team when a legal entity is created, and how many weeks of notice that promise is worth.