The Structure of a Workflow Definition
A workflow definition is a declarative blueprint that describes how IM events should be processed. The definition consists of four parts:
- Metadata: Name, description, version, status (active/inactive).
- Trigger: The IM event that initiates the workflow.
- Conditions: Optional Groovy logic that filters which triggered records actually start a workflow instance.
- Steps: The sequence of actions the workflow takes (approval, notification, Groovy script, REST call, etc.).
Here's a simplified JSON-style representation of a workflow definition structure:
{
"name": "Plan Approval Workflow",
"description": "Route compensation plans through manager and director approval",
"version": "1.2",
"status": "ACTIVE",
"triggerEvent": "plan.submitted_for_approval",
"conditions": {
"filterLogic": "AND",
"rules": [
{ "field": "plan.type", "operator": "=", "value": "INCENTIVE" },
{ "field": "plan.status", "operator": "=", "value": "SUBMITTED" }
]
},
"steps": [
{ ... step 1 definition ... },
{ ... step 2 definition ... }
]
}
Trigger Events: What IM Events Can Fire Workflows
A workflow definition must specify which IM event triggers it. Advanced Workflow listens for these events published by IM. When an event occurs that matches the trigger event, a new workflow instance is created.
Common IM trigger events (not exhaustive):
Plan Events:
plan.created— A new plan is created in IM.plan.submitted_for_approval— A plan is submitted to the approval chain.plan.approved— A plan completes approval and becomes active.plan.rejected— A plan approval is rejected.plan.activated— A plan is activated (becomes effective for participants).plan.deactivated— A plan is deactivated (end of period).
Calculation Events:
calculation.started— A calculation run begins.calculation.completed— A calculation run finishes (all periods/participants processed).calculation.failed— A calculation run terminates with errors.
Statement Events:
statement.generated— A statement is created for a participant in a period.statement.published— A statement is published and visible to the participant.statement.updated— A statement is revised (correction after initial publication).
Quota/Participant Events:
quota.updated— A participant's quota is changed.participant.updated— Participant master data is changed (manager, territory, etc.).participant.hired— A new participant is added to the system.participant.terminated— A participant is terminated.
Dispute Events:
dispute.submitted— A participant files a dispute.dispute.resolved— A dispute is approved or rejected.
Period Events:
period.opened— A new compensation period is opened.period.closed— A period is closed (no more changes).period.finalized— A period is finalized and archived.
Conditions: Filtering Which Events Trigger Workflows
A trigger event fires frequently. For example, plan.submitted_for_approval fires every time any plan is submitted — potentially dozens of times a day. Without conditions, every plan submission would trigger the workflow, even if you only want to trigger for certain plan types or regions.
Conditions allow you to filter which triggered events actually create a workflow instance. Conditions are Groovy logic that evaluates the triggered record's data and returns true (start workflow) or false (skip workflow).
Simple Condition Example
Trigger: plan.submitted_for_approval
Condition: Only trigger if plan.type = "INCENTIVE" (ignore administrative plans).
// Condition logic (Groovy) plan.type == "INCENTIVE"
Complex Condition Example
Trigger: statement.published
Condition: Only trigger if the statement commission > $1000 AND the participant is in the US region.
// Condition logic (Groovy) statement?.commissionAmount != null && statement.commissionAmount > 1000 && participant?.region == "US"
Notice the null-safe navigation (?.) to prevent NullPointerException if statement or participant is missing.
Conditions with Negation
Trigger: participant.updated
Condition: Trigger only if the participant is NOT being terminated (don't process workflows for terminated participants).
// Condition logic (Groovy) participant?.status != "TERMINATED" && participant?.status != "INACTIVE"
Steps: The Building Blocks of Workflow Logic
A workflow definition is a sequence of steps. Each step is an action: approval, notification, Groovy script, REST API call, or a decision point. The workflow executes steps in order unless a condition causes a branch or return.
Step anatomy:
- Name: Human-readable label for the step ("Manager Approval", "Send Confirmation Email", "Validate Quota").
- Type: What the step does (APPROVAL, NOTIFICATION, GROOVY_SCRIPT, REST_CALL, CONDITION, etc.).
- Configuration: Type-specific settings (for approval: assignee rule, SLA, escalation; for notification: recipients, subject, body; for Groovy: script code).
- Transition Rules: What happens when the step completes (go to next step, return to previous, end workflow, branch based on condition).
Approval Steps: Assignee Rules
An approval step requires you to specify who the approver is. Advanced Workflow supports three assignee rule types:
Assignee Rule Type 1: Specific User
Hard-code a user ID or email.
Configuration:
assigneeRule: {
type: "SPECIFIC_USER",
userId: "compensation-director@company.com"
}
Use case: Static approval paths. Example: All high-value exceptions always escalate to the compensation director, not to the participant's manager.
Risk: If the user leaves, you must manually update the workflow definition.
Assignee Rule Type 2: Role-Based Lookup
Find users by role. The workflow looks up all users with a specific role and assigns the approval to them (or to any one of them).
Configuration:
assigneeRule: {
type: "ROLE_BASED",
roleName: "Compensation_Analyst",
region: "EMEA" // optional: filter to a specific region
}
Use case: When multiple people can approve and any one is acceptable. Example: Assign dispute approvals to anyone with the "Compensation_Analyst" role in the participant's region.
Risk: If no user has the role, assignment fails.
Assignee Rule Type 3: Hierarchy-Based Lookup (Most Common in IM)
Navigate the participant's org hierarchy to find the approver. This is the most common pattern in IM because approval chains almost always follow reporting lines.
Configuration:
assigneeRule: {
type: "HIERARCHY_BASED",
path: "participant.manager.id" // Direct manager
}
// Variations:
path: "participant.manager.manager.id" // Skip-level (director)
path: "participant.manager.manager.manager.id" // Two levels up
Use case: Standard approval chains. Manager approves first, then director for high-value items.
Risk: If the manager ID is null, the assignment fails and the workflow hangs. Robust workflows include null checks and fallback rules.
Hierarchy Assignment with Fallback Logic
Robust approval workflows use Groovy to implement fallback logic: if the direct manager is missing, escalate to the director. If the director is missing, escalate to the VP. If no one is found, escalate to the compensation team.
// Groovy assignee rule with fallback
def approver = null
if (participant?.manager?.id) {
approver = participant.manager.id
} else if (participant?.manager?.manager?.id) {
approver = participant.manager.manager.id
} else if (participant?.businessUnit?.head?.id) {
approver = participant.businessUnit.head.id
} else {
approver = "compensation-team@company.com"
}
return approver
This script tries to find an approver in order of preference. If all fail, it defaults to the compensation team email. This prevents the workflow from hanging due to missing hierarchy data.
Notification Steps: Recipients and Templates
A notification step sends a message to one or more recipients. Configuration includes:
Recipient Definition (Groovy):
// Always send to participant
recipients = [participant.email]
// Conditionally add manager if commission is high
if (statement?.commissionAmount > 10000) {
recipients.add(participant.manager.email)
}
// Conditionally add compensation team if outlier detected
if (isOutlier(statement.commissionAmount)) {
recipients.add("compensation-team@company.com")
}
return recipients
Message Template:
subject: "Your ${plan.period} Statement is Ready"
body:
Hi ${participant.name},
Your compensation statement for ${plan.period} is now available.
Commission Amount: $${formatCurrency(statement.commissionAmount)}
Quota Achievement: ${statement.quotaAchievement}%
Territory: ${quota.territory}
Log in to SuccessFactors to view your detailed statement breakdown.
Questions? Contact: ${participant.manager.email} or compensation-team@company.com
Best regards,
Compensation Team
Context variables (${...}) are replaced at runtime with actual participant and plan data. Common variables:
${participant.name},${participant.email},${participant.id},${participant.manager.name}${plan.name},${plan.period},${plan.type},${plan.startDate},${plan.endDate}${statement.commissionAmount},${statement.quotaAchievement},${statement.bonus}${workflow.createdAt},${workflow.completedAt},${workflow.submittedBy}
Groovy Script Steps: Custom Logic
A Groovy script step executes custom code. Groovy steps are used for:
- Data transformations (convert commission to currency format, calculate percentage change).
- Validations (check if participant is eligible, check if quota is valid).
- Complex routing logic (determine approver based on multiple conditions).
- External API calls (call payroll system, CRM, data warehouse).
Example Groovy Step: Validate and Route to Correct Approver
// Groovy script step in an approval workflow
// Determines if we should send to manager or director based on plan value
import groovy.json.JsonSlurper
def approvingRole = null
if (plan?.annualValue == null) {
log.error("Plan has no annual value: " + plan.id)
approvingRole = "ESCALATE_TO_DIRECTOR"
} else if (plan.annualValue < 100000) {
approvingRole = "MANAGER"
} else if (plan.annualValue < 500000) {
approvingRole = "DIRECTOR"
} else {
approvingRole = "VP"
}
// Store for use in next step
workflow.approvalLevel = approvingRole
return approvingRole
Context Variables Reference Table
| Category | Variable | Type | Example Value |
|---|---|---|---|
| Participant | ${participant.id} |
String | P12345 |
| Participant | ${participant.name} |
String | John Smith |
| Participant | ${participant.email} |
String | john.smith@company.com |
| Participant | ${participant.manager.id} |
String | M98765 |
| Participant | ${participant.region} |
String | EMEA, APAC, AMER |
| Plan | ${plan.id} |
String | PLAN_Q2_2026 |
| Plan | ${plan.name} |
String | Q2 2026 Sales Incentive |
| Plan | ${plan.period} |
String | Q2 2026 |
| Plan | ${plan.annualValue} |
Decimal | 250000.00 |
| Statement | ${statement.commissionAmount} |
Decimal | 5240.50 |
| Statement | ${statement.quotaAchievement} |
Decimal | 115.5 |
| Workflow | ${workflow.id} |
String | WF_UUID_123456 |
| Workflow | ${workflow.createdAt} |
ISO8601 DateTime | 2026-04-12T09:15:00Z |
| Workflow | ${workflow.submittedBy} |
String (User ID) | admin@company.com |
Complete Worked Example: Two-Step Plan Approval Workflow
Here's a complete workflow definition for a realistic scenario: a compensation plan must be approved by the manager, then the director (if plan value > $500K).
Workflow Definition
name: "Plan Approval Workflow"
description: "Route compensation plans through manager and director approval"
version: "1.0"
status: "ACTIVE"
triggerEvent: "plan.submitted_for_approval"
conditions:
# Only trigger for incentive plans, not admin plans
filterLogic: "AND"
rules:
- field: "plan.type"
operator: "="
value: "INCENTIVE"
- field: "plan.status"
operator: "="
value: "SUBMITTED"
steps:
- stepNumber: 1
name: "Manager Approval"
type: "APPROVAL"
description: "Route to the participant's direct manager for initial review"
assigneeRule:
type: "HIERARCHY_BASED"
path: "participant.manager.id"
sla:
duration: 3
unit: "BUSINESS_DAYS"
timeZone: "participant.timeZone"
escalation:
afterDays: 3
action: "ESCALATE_TO_MANAGER_MANAGER"
escalateToPath: "participant.manager.manager.id"
taskSubject: "Plan Approval Needed: ${plan.name}"
taskBody: |
Hi ${participant.manager.name},
${participant.name} has submitted their compensation plan for Q2 2026 approval.
Plan Value: $${formatCurrency(plan.annualValue)}
Please review and approve or reject.
onApprove:
nextStep: 2
onReject:
action: "END_WORKFLOW"
notifyInitiator: true
message: "Manager has rejected the plan submission."
- stepNumber: 2
name: "Conditional Director Review"
type: "GROOVY_SCRIPT"
description: "Check plan value. If >= $500K, route to director. Otherwise skip to final notification."
script: |
if (plan?.annualValue >= 500000) {
workflow.requiresDirectorApproval = true
return "DIRECTOR_REVIEW"
} else {
workflow.requiresDirectorApproval = false
return "SKIP_TO_NOTIFICATION"
}
transitions:
DIRECTOR_REVIEW: 3
SKIP_TO_NOTIFICATION: 4
- stepNumber: 3
name: "Director Approval"
type: "APPROVAL"
description: "High-value plans require director sign-off"
condition: "workflow.requiresDirectorApproval == true"
assigneeRule:
type: "HIERARCHY_BASED"
path: "participant.manager.manager.id"
sla:
duration: 5
unit: "BUSINESS_DAYS"
escalation:
afterDays: 5
action: "AUTO_APPROVE"
taskSubject: "High-Value Plan Approval: ${plan.name} ($${formatCurrency(plan.annualValue)})"
taskBody: |
Hi ${participant.manager.manager.name},
A high-value plan requires your approval.
Participant: ${participant.name}
Plan: ${plan.name}
Value: $${formatCurrency(plan.annualValue)}
Manager approved on ${workflow.step1CompletedAt}.
Please approve or return for revision.
onApprove:
nextStep: 4
onReject:
action: "RETURN_TO_PREVIOUS_STEP"
previousStep: 1
message: "Director requests changes. Manager please revise and resubmit."
- stepNumber: 4
name: "Send Approval Notification"
type: "NOTIFICATION"
description: "Notify participant that plan has been approved"
recipientScript: |
recipients = [participant.email]
if (participant.manager?.email) {
recipients.add(participant.manager.email)
}
return recipients
channel: "EMAIL"
subject: "Plan Approved: ${plan.name}"
body: |
Hi ${participant.name},
Your compensation plan for ${plan.period} has been approved and is now active.
Plan Name: ${plan.name}
Period: ${plan.period}
Start Date: ${plan.startDate}
Log in to SuccessFactors to view plan details and target commission structure.
Questions? Contact your manager or the compensation team.
onComplete:
action: "END_WORKFLOW"
workflowOutcome: "APPROVED"
returnToIM: true
Testing Workflow Definitions Before Activation
Before you activate a workflow definition in production, you must test it in a sandbox environment. Testing includes:
Sandbox Run with Test Participant: Create a test participant in the sandbox who has a complete hierarchy (manager, director, VP). Submit a test plan that triggers the workflow. Walk through each approval step. Verify:
- Workflow fires on the correct trigger event.
- Conditions work as expected (workflow fires for INCENTIVE plans, doesn't fire for ADMIN plans).
- Approvers are correctly identified (first approver is the manager, second is the director for high-value plans).
- Approval tasks appear in the correct approver's inbox.
- Task subject and body contain correct context variables (participant name, plan name, etc.).
- Approval actions work (Approve moves to next step, Reject ends workflow).
- SLA timers are set correctly.
- Escalation logic works (after X days, escalate or auto-approve).
Edge Case Testing: Test scenarios where hierarchy data is missing or unexpected:
- Test participant with no manager. Verify fallback logic routes to director or compensation team.
- Test participant whose manager is also a participant. Verify no circular routing.
- Test participant with terminated manager. Verify escalation skips terminated user.
- Test boundary conditions (plan.annualValue = exactly 500000, achievement = exactly 100%, etc.).
Workflow Versioning: A Critical Operational Detail
Workflow versioning is one of the most important concepts in Advanced Workflow operations, and it's often misunderstood.
Key rule: In-flight workflow instances run on the version of the definition that was active when the instance started. Updating the definition does not affect running instances.
Example scenario:
- Monday 9am: Workflow "Plan Approval" v1.0 is active. Participant A submits a plan. A workflow instance (Instance #1) is created running on v1.0.
- Monday 5pm: You discover a bug in v1.0 (approver name is wrong in the task subject). You create v1.1 with the bug fix and activate it.
- Tuesday 9am: Participant B submits a plan. A workflow instance (Instance #2) is created running on v1.1 (the new version).
- Tuesday 2pm: Instance #1 (Participant A's workflow) still runs on v1.0 with the bug. The manager sees the incorrect approver name in the task. Instance #2 (Participant B's workflow) runs on v1.1 and the manager sees the correct name.
This creates inconsistency. Participants submitted on different days see different task wording, different business logic, different approval requirements.
Strategies to minimize versioning pain:
Strategy 1: Get it right before activation. Spend enough time in sandbox testing that v1.0 is correct. Minimize the need to release v1.1, v1.2, etc. This is the ideal approach.
Strategy 2: Minor fixes only in new versions. Reserve new versions for bug fixes and wording changes. Don't change business logic (e.g., approval chain, routing) in new versions — this creates inconsistency between instances.
Strategy 3: Drain in-flight instances before major changes. If you must change business logic, wait until all in-flight instances complete. Then activate the new version. This is slow but ensures consistency.
Strategy 4: Plan versioning in advance. Design the workflow to be extensible. Use Groovy to encapsulate business logic so you can change behavior without changing the workflow structure. This allows you to update logic in v1.1 without affecting the approval chain structure.
Activation and Deployment
Once a workflow definition is tested and ready, you activate it. Activation makes the workflow live — it will trigger on the specified event and process real participants and plans.
Deployment checklist before activation:
- All sandbox test cases passed.
- All approver hierarchies are correct in production.
- All email addresses and role configurations are correct.
- Notification templates reviewed and approved.
- SLA timers and escalation rules validated.
- Groovy scripts error-handled (no unguarded null accesses).
- Documentation created (what the workflow does, how to troubleshoot, who to contact if stuck).
- Support team trained on handling stuck approvals.
Key Takeaways
- A workflow definition is a blueprint: trigger event → conditions → steps. Each step is an action with assignees, transitions, and error handling.
- Trigger events come from IM. Conditions filter which triggered records create workflow instances using Groovy logic.
- Assignee rules determine who receives approval tasks: specific user, role-based lookup, or hierarchy-based lookup (most common in IM).
- Context variables (${participant.name}, ${plan.period}, ${statement.commissionAmount}, etc.) personalize approval tasks and notification content.
- Workflow versioning is critical: in-flight instances run on the version active when they started. Updating the definition applies to new instances only.
- Spend 2–3 weeks testing workflows in sandbox before production activation. Test normal cases and edge cases (missing hierarchy, circular reporting, etc.).