This workflow corresponds to n8n.io template #14509 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"id": "node-trigger",
"name": "When clicking 'Test workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-384,
448
],
"parameters": {},
"typeVersion": 1
},
{
"id": "node-config",
"name": "Configuration",
"type": "n8n-nodes-base.set",
"position": [
-160,
448
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-api-base",
"name": "SF_API_BASE_URL",
"type": "string",
"value": "https://apiXX.successfactors.com/odata/v2"
},
{
"id": "cfg-idp",
"name": "SF_IDP_URL",
"type": "string",
"value": "https://apiXX.successfactors.com/oauth/idp"
},
{
"id": "cfg-token",
"name": "SF_TOKEN_URL",
"type": "string",
"value": "https://apiXX.successfactors.com/oauth/token"
},
{
"id": "cfg-company",
"name": "company_id",
"type": "string",
"value": "<your SF company ID>"
},
{
"id": "cfg-client",
"name": "client_id",
"type": "string",
"value": "<API Key from OAuth2 client registration>"
},
{
"id": "cfg-user",
"name": "user_id",
"type": "string",
"value": "<SF user ID to authenticate as>"
},
{
"id": "cfg-key",
"name": "private_key",
"type": "string",
"value": "<base64 key body \u2014 from PEM file, no BEGIN/END lines, no line breaks>"
},
{
"id": "cfg-top",
"name": "top",
"type": "number",
"value": 20
},
{
"id": "cfg-select",
"name": "select",
"type": "string",
"value": "personIdExternal,perPersonUuid"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "node-get-saml",
"name": "Get SAML Assertion",
"type": "n8n-nodes-base.httpRequest",
"position": [
64,
448
],
"parameters": {
"url": "={{ $('Configuration').first().json.SF_IDP_URL }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "client_id",
"value": "={{ $('Configuration').first().json.client_id }}"
},
{
"name": "user_id",
"value": "={{ $('Configuration').first().json.user_id }}"
},
{
"name": "token_url",
"value": "={{ $('Configuration').first().json.SF_TOKEN_URL }}"
},
{
"name": "private_key",
"value": "={{ $('Configuration').first().json.private_key }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "node-get-token",
"name": "Get Bearer Token",
"type": "n8n-nodes-base.httpRequest",
"position": [
288,
448
],
"parameters": {
"url": "={{ $('Configuration').first().json.SF_TOKEN_URL }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "company_id",
"value": "={{ $('Configuration').first().json.company_id }}"
},
{
"name": "client_id",
"value": "={{ $('Configuration').first().json.client_id }}"
},
{
"name": "grant_type",
"value": "urn:ietf:params:oauth:grant-type:saml2-bearer"
},
{
"name": "assertion",
"value": "={{ $json.data }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "node-fetch",
"name": "Fetch PerPerson from SF",
"type": "n8n-nodes-base.httpRequest",
"position": [
512,
448
],
"parameters": {
"url": "={{ $('Configuration').first().json.SF_API_BASE_URL }}/PerPerson",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "$format",
"value": "json"
},
{
"name": "$top",
"value": "={{ $('Configuration').first().json.top }}"
},
{
"name": "$select",
"value": "={{ $('Configuration').first().json.select }}"
},
{
"name": "$expand",
"value": "employmentNav"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.access_token }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "node-flatten",
"name": "Flatten Results",
"type": "n8n-nodes-base.code",
"position": [
736,
448
],
"parameters": {
"jsCode": "// Unpack the OData v2 d.results wrapper from the SF API response\nconst persons = $input.first().json?.d?.results ?? [];\nconst output = [];\n\nfor (const person of persons) {\n const employments = person.employmentNav?.results ?? [];\n\n if (employments.length === 0) {\n // Include persons who have no employment record\n output.push({\n json: {\n personIdExternal: person.personIdExternal ?? null,\n perPersonUuid: person.perPersonUuid ?? null,\n empStartDate: null,\n empEndDate: null,\n userId: null,\n }\n });\n } else {\n for (const emp of employments) {\n output.push({\n json: {\n personIdExternal: person.personIdExternal ?? null,\n perPersonUuid: person.perPersonUuid ?? null,\n empStartDate: emp.startDate ?? null,\n empEndDate: emp.endDate ?? null,\n userId: emp.userId ?? null,\n }\n });\n }\n }\n}\n\nreturn output;"
},
"typeVersion": 2
},
{
"id": "sticky-overview",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1168,
-144
],
"parameters": {
"color": 3,
"width": 668,
"height": 936,
"content": "## SuccessFactors read Person & Employment \u2014 via SAML 2.0 Bearer Assertion based on OAuth 2 configuration.\n\n**Note:** SuccessFactors uses a proprietary **SAML 2.0 Bearer Assertion** flow \u2014 n8n's built-in OAuth2 credential does not work here. Basic Authentication is not recommended as a secure authentication mechanism.\n\n### Flow\n1. **Configuration** \u2014 URLs, credentials, private key\n2. **Get SAML Assertion** \u2192 POST to `/oauth/idp` \n3. **Get Bearer Token** \u2192 POST to `/oauth/token`\n4. **Fetch PerPerson** \u2192 OData v2 GET with Bearer token\n5. **Flatten Results** \u2192 one item per person-employment record\n\nExample URLs can be found in configuration node.\n\n### Setup\n1. Register OAuth2 client in SF Admin \u2192 `Go to Manage OAuth2 Client` Applications. Generate a new certificate from there. \n2. `Register new client application` and provide an application name and URL which are easy to understand. Both can be virtual and must not exist.\n3. Optional step: you can limit the client registration to a single user, which permissions will be relevant for the API in SuccessFactors. I recommend keeping this empty, since the SAML Assertion requires to limit on a user too.\n4. Press `Generate X.509 Certificate` and provide at least a virtual Common Name (CN) and limit the validity of the certificate to your needs.\n5. When generated you got the necessary certificate and **API Key** which is our `client_id`.\n6. Export Certificate.pem from the OAuth 2 Client registration in SuccessFactors. Open with a text editor and copy base64 content between `<redacted-credential>\n` and `-----END ENCRYPTED PRIVATE KEY-----`. \n--> That is your `private_key`.\n3. Fill in the Configuration node.\n\n### Extend\n- Add `personalInfoNav`, `jobInfoNav` to ``\n- Loop + `` for full pagination\n- Replace Manual Trigger with Schedule Trigger"
},
"typeVersion": 1
},
{
"id": "sticky-section-1",
"name": "Section: Configure",
"type": "n8n-nodes-base.stickyNote",
"position": [
-432,
32
],
"parameters": {
"color": 0,
"width": 400,
"height": 374,
"content": "### 1 \u2014 Configure\n- `SF_API_BASE_URL` \u2014 OData v2 base URL\n- `SF_IDP_URL` \u2014 assertion endpoint\n- `SF_TOKEN_URL` \u2014 token endpoint\n- `company_id` \u2014 SF tenant ID\n- `client_id` \u2014 API Key from OAuth2 registration\n- `user_id` \u2014 **Mandatory.** SF user the token is issued for. Data access is scoped to this user's permissions \u2014 use a service account.\n- `private_key` \u2014 base64 body from the exported `.pem` (no headers, no line breaks)\n- `top` / `select` \u2014 OData query params"
},
"typeVersion": 1
},
{
"id": "sticky-section-2",
"name": "Section: Get Token",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
32
],
"parameters": {
"color": 0,
"width": 400,
"height": 367,
"content": "### 2 \u2014 Get Token\n**Get SAML Assertion** \u2014 POSTs `client_id`, `user_id`, `token_url`, `private_key` to `/oauth/idp`. Returns a signed SAML 2.0 XML assertion.\n\n**Get Bearer Token** \u2014 POSTs assertion + `company_id` + `client_id` to `/oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer`. Returns `access_token`."
},
"typeVersion": 1
},
{
"id": "sticky-section-3",
"name": "Section: Fetch & Flatten",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
32
],
"parameters": {
"color": 0,
"width": 400,
"height": 369,
"content": "### 3 \u2014 Fetch & Flatten\n**Fetch PerPerson** \u2014 GET `/PerPerson?=employmentNav` with `Authorization: Bearer <token>`.\n\n**Flatten Results** \u2014 unpacks `d.results` and `employmentNav.results`. Outputs one item per person-employment combination."
},
"typeVersion": 1
}
],
"connections": {
"Configuration": {
"main": [
[
{
"node": "Get SAML Assertion",
"type": "main",
"index": 0
}
]
]
},
"Get Bearer Token": {
"main": [
[
{
"node": "Fetch PerPerson from SF",
"type": "main",
"index": 0
}
]
]
},
"Get SAML Assertion": {
"main": [
[
{
"node": "Get Bearer Token",
"type": "main",
"index": 0
}
]
]
},
"Fetch PerPerson from SF": {
"main": [
[
{
"node": "Flatten Results",
"type": "main",
"index": 0
}
]
]
},
"When clicking 'Test workflow'": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Configure SuccessFactors OAuth2 SAML client within SuccessFactors Get SAML assertion, exchange for bearer token for API connection Fetch PerPerson data via OData v2 Flatten to person-employment records as an example call to SuccessFactors API It takes ~10-20 minutes to register…
Source: https://n8n.io/workflows/14509/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)
This automation organizes your n8n workflows files into categorizes (Active, Template, Done, Archived) and uploads them directly to a categorized Google Drive folders. It is designed to help users man
Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.