Entra (Microsoft Graph)
Wayd.Integrations.MicrosoftGraph synchronizes people (employees and contingent workers) from Microsoft Entra ID into Wayd's Employee table. Each Entra connection talks to a single Entra tenant; multiple connections per deployment are supported (e.g. one per acquired company's tenant), but only one People Sync connection across all PeopleSync connectors may be active at a time — see Cross-cutting → single-active rule.
- Category: People sync
- Status: GA
- Auth: Microsoft Entra App Registration (client credentials flow)
- Sync direction: Entra → Wayd
Capabilities
- Pull member users (
userType eq 'Member'; guests are never synced) from a single Entra tenant via Microsoft Graph - Optionally scope to a single Entra group (transitive members) instead of all member users
- Optionally include disabled accounts
- Admin-selectable "Normalize Name Casing" — title-cases names that come back from Entra in all-caps, handling Mc/Mac, apostrophes (O', D'), and hyphenated names correctly. Mixed-case input is preserved. Default on
- Upsert into Wayd
Employeerecords, with the manager hierarchy from each user'smanager(see Field mapping for when it resolves) - Deactivate Wayd employees not present in the latest payload. The deactivation pass treats absence from the sync result as "no longer employed"; this is safe because the single-active People Sync rule guarantees exactly one source of truth at a time
The Entra source is full-snapshot only: EntraEmployeeSource.SupportsIncremental is false, so every run reads the whole user set. When the admin requests a Differential People Sync, the runner transparently falls back to a Full snapshot for Entra connections.
Setup
- In the Entra admin portal, register an application for Wayd. Note the Tenant ID and Application (client) ID.
- Under Certificates & secrets, create a client secret. Copy the value immediately — Entra only shows it once.
- Grant the Application permissions listed in Required permissions below. Admin-consent them after granting.
- (Optional) If you want to sync only a subset of users, create or pick an Entra group containing those users and copy its Object ID.
- In Wayd, open Settings → System → Connections and click Create Connection. Pick Entra.
- Fill in:
- Tenant ID — the directory tenant ID.
- Client ID — the app registration's application ID.
- Client Secret — the value from step 2.
- All Users Group Object ID (optional) — the group whose transitive members to sync. Leave blank to sync every member user in the tenant.
- Match Employees By —
Email(default; cross-source stable) orEmployee Number. - Include Disabled Users — default off.
- Normalize Name Casing — default on.
- Save. The connection is active once created — if another People Sync connection is already active, deactivate one of them (see the single-active rule). Wayd does not test the credentials on save; a wrong tenant, client ID, or secret shows up as a failed sync run.
- Click Full Sync (or Diff Sync, which runs as Full for Entra) in the detail page's Sync History section, or have an admin schedule People Full Sync / People Differential Sync under Settings → System → Background Jobs — no sync is scheduled by default.
Required permissions
The Entra app registration needs the following Application permissions (not Delegated) on Microsoft Graph, with admin consent granted:
| Permission | Why Wayd needs it |
|---|---|
| User.Read.All | Enumerate users in the tenant; read profile fields (display name, mail, manager, department, job title, etc.) |
| GroupMember.Read.All | Only required when "All Users Group Object ID" is set — read transitive group membership to scope the sync |
User.Read.All is the only mandatory grant for the default "sync everyone" mode. Adding GroupMember.Read.All is what unlocks the group-scoped filter.
Wayd does not write back to Entra. There is no User.ReadWrite or similar elevation.
Configuration options
| Option | Default | Notes |
|---|---|---|
| Tenant ID | — | Required. Directory tenant GUID. |
| Client ID | — | Required. App registration's application ID. |
| Client Secret | — | Required. Encrypted at rest; on edit, leave blank to keep the stored secret. |
| All Users Group Object ID | (blank) | Optional. Scope sync to transitive members of this group. |
| Match Employees By | The preferred key for the BulkUpsertEmployeesCommand matcher; it falls back to the other candidate key when the preferred one misses, so an upstream email or employee-number change never creates a duplicate row. Email is cross-source stable; switch to Employee Number only if both your connectors emit the same stable identifier. | |
| Include Disabled Users | Off | When on, accounts with accountEnabled=false are also imported. |
| Normalize Name Casing | On | Title-cases all-caps names; preserves mixed-case input. |
Field mapping
EntraEmployee projects each Graph user onto the employee record:
| Wayd employee | Microsoft Graph user | Notes |
|---|---|---|
| Employee Number | employeeId, else id (object id) | |
| First / last name | givenName / surname | No middle name. Users missing either are dropped. Title-cased when Normalize Name Casing is on. |
mail, else userPrincipalName | ||
| Work emails | proxyAddresses | See step 4 below. The email above is always included. |
| Hire date | hireDate, else employeeHireDate | |
| Job title, department, office location | jobTitle, department, officeLocation | |
| Employee type | employeeType | Passed through as entered in the tenant. |
| Active | accountEnabled | |
| Manager | manager (its object id) | Matched against Employee Number, so it resolves only for a manager whose Employee Number is their object id — one with no employeeId. |
How it works
- An admin creates an Entra Connection in Settings → System → Connections, supplying tenant ID, client ID, client secret (encrypted at rest), and optionally a group object ID.
- The
PeopleSyncRunnerreads all active connections with theConnectorCapability.Peoplecapability and resolves each connector'sIEmployeeSourcevia keyed DI — for Entra that's theEntraEmployeeSourceadapter, which builds a per-connectionGraphServiceClientfrom the stored credentials. There is no shared app-wide Graph client for sync. - The
MicrosoftGraphServicesource pulls member users — from/users, or from the group'stransitiveMemberswhen a group is set — filtered server-side onuserType eq 'Member'and, unless disabled users are included,accountEnabled eq true. It pages through Graph's next-link cursor, 100 users per page, drops users missinggivenName/surname, and returnsEntraEmployeerecords. If no users come back, the run fails with "No employees found in Entra via Microsoft Graph". - Each user's
proxyAddressesbecome that employee's work email collection. Exchange demotes a previous primary to a secondary rather than deleting it, so this is where an address someone used before a tenant or domain move survives — which is what lets Wayd match them against systems still referencing it (an Azure DevOps work item, for instance). OnlySMTP:/smtp:entries are kept: other protocols (X500:,SIP:,EUM:) are not email addresses, and the auto-generated*.onmicrosoft.com/*.microsoftonline.comrouting addresses are discarded as noise.otherMailsis deliberately not read — it holds personal recovery addresses. - The connector-agnostic
BulkUpsertEmployeesCommandreconciles those records against theEmployeetable. Any active employee row not matched against the payload is deactivated — safe because the single-active People Sync rule guarantees one source of truth at a time. (The runner additionally aborts loudly if more than one active People Sync connection ever slips through, as a defense-in-depth check.) The work-email collection is reconciled the same way: Entra owns it, so an address it stops reporting is removed. - Each run is captured as a
SyncRunrow; per-run details (employeesFetched,employeesUpserted,employeesSkipped,errors[]) are stored as JSON and rendered in the Sync History section.
Troubleshooting
- Sync run fails with "Error getting employees from Entra tenant … via Microsoft Graph" — Graph rejected or failed the request; the run records only this message, and the Graph error itself (for example
Authorization_RequestDeniedwhen admin consent wasn't granted on User.Read.All, or an expired client secret) is in the API's error log. For missing consent, have a tenant admin click Grant admin consent in the app registration. - Sync run fails with "No employees found in Entra via Microsoft Graph" — the query returned no member users. Verify the app registration is enabled and the client secret isn't expired. If using the group filter, confirm the group has direct or transitive member users (not just service principals or guests).
- Some users missing — Wayd drops Entra users missing
givenNameorsurnameto avoid blankPersonNamerecords. They are dropped before the upsert, so they are not in the run's fetched or skipped counts; only the API log's "Found … members" / "Returning … employees" pair shows the difference. - Managers not linked — when users carry an
employeeId, the manager reference (an object id) does not match their Employee Number; see Field mapping. - Names look ALL CAPS in Wayd — turn on Normalize Name Casing in the connection config; default is on, but if you disabled it the source casing wins.
Cross-cutting
See Integrations → Cross-cutting concerns for credential encryption, activation, sync history, and the single-active People-sync rule.