Introduction: Why Patterns Matter
In every IM implementation, the same business scenarios appear across different organizations. A compensation team needs to approve plans before they go live. Participants need to dispute quotas or commission amounts. Outlier calculations need human review. Statements need to be automatically published when ready. These scenarios are so common that they form patterns — reusable workflow configurations that you adapt to your specific business context.
Understanding patterns accelerates your design phase. Instead of building workflows from scratch, you recognize the pattern, apply the standard configuration with your customizations, and move on. Patterns also provide guidance for edge cases — the most common failure modes are well-documented in each pattern.
Pattern 1: Hierarchical Plan Approval
Overview
A compensation plan is submitted for approval. The workflow routes it through the participant's reporting hierarchy: manager → director → VP (if amount exceeds threshold) → final approval. At each step, the approver can approve, reject, or return for revision. Once all approvals are obtained, the plan is activated and a notification is sent to the participant.
This is the most common pattern in IM. Almost every implementation requires it. Variations include the number of approval levels, the value thresholds that trigger additional approvers, and the escalation rules.
Configuration Walkthrough
Trigger Event: plan.submitted_for_approval
Conditions:
- Only trigger for plan.type = "INCENTIVE" (exclude admin plans).
- Only trigger if plan.status = "SUBMITTED".
Step 1 — Manager Review:
- Assignee: participant.manager.id
- SLA: 3 business days
- Task subject: "Plan Approval: ${plan.name} ($${formatCurrency(plan.annualValue)})"
- Task body: Include plan details and request approval/rejection.
- Escalation: After 3 days, escalate to participant.manager.manager.id (director).
- On Approve: Advance to Step 2 (value check).
- On Reject: End workflow. Notify participant that manager rejected the plan.
Step 2 — Conditional Value Check (Groovy Script):
// Check plan value to determine if director approval is needed
if (plan?.annualValue >= 500000) {
workflow.requiresDirectorApproval = true
return "DIRECTOR_APPROVAL"
} else {
workflow.requiresDirectorApproval = false
return "FINAL_NOTIFICATION"
}
Step 3 — Director Approval (conditional):
- Only execute if workflow.requiresDirectorApproval = true.
- Assignee: participant.manager.manager.id (director).
- SLA: 5 business days.
- Escalation: After 5 days, escalate to participant.manager.manager.manager.id (VP).
- On Approve: Advance to Step 4 (notification).
- On Reject: Return to Step 1 (manager revises plan).
Step 4 — Final Notification:
- Type: Notification.
- Recipients: participant.email, participant.manager.email.
- Subject: "Your Plan Has Been Approved: ${plan.name}"
- Body: Include plan details and link to SuccessFactors.
- On Complete: Workflow outcome = "APPROVED". Return to IM. IM updates plan.status = "ACTIVE".
Edge Cases and Solutions
Edge Case 1: Participant Has No Manager
Symptom: Workflow hangs in Step 1 with no approver assigned.
Cause: participant.manager.id is null.
Solution: Implement fallback logic in assignee rule. If manager is null, escalate to the business unit head or compensation team.
// Groovy assignee rule with fallback
def approver = null
if (participant?.manager?.id) {
approver = participant.manager.id
} else if (participant?.businessUnit?.head?.id) {
approver = participant.businessUnit.head.id
} else {
approver = "compensation-team@company.com"
}
return approver
Edge Case 2: Manager Is Also a Participant (Circular Relationship)
Symptom: Plan approval routes to the participant themselves.
Cause: The participant's manager field points to a participant who is also in the plan.
Solution: Detect circular relationships and skip to the next level in hierarchy.
// Detect circular relationship
def approver = null
if (participant?.manager?.id && participant.manager.id != participant.id) {
approver = participant.manager.id
} else if (participant?.manager?.manager?.id && participant.manager.manager.id != participant.id) {
approver = participant.manager.manager.id
} else {
approver = "compensation-team@company.com"
}
return approver
Edge Case 3: Manager Is Terminated or Inactive
Symptom: Approval task is assigned to a terminated manager who never responds.
Cause: Manager's user account is inactive but still listed as participant's manager in hierarchy data.
Solution: Validate manager's active status before assignment. Escalate if manager is inactive.
// Check manager status
def approver = null
if (participant?.manager?.id && participant.manager.status == "ACTIVE") {
approver = participant.manager.id
} else if (participant?.manager?.manager?.id && participant.manager.manager.status == "ACTIVE") {
approver = participant.manager.manager.id
} else {
approver = "compensation-team@company.com"
}
return approver
Edge Case 4: Manager Out of Office During Approval Window
Symptom: Manager doesn't respond within SLA, escalation triggers auto-approval or escalation to next level.
Solution: This is why escalation rules exist. Set reasonable SLA timers (3–5 business days) and escalation rules. A 5-day SLA gives the manager time to return from time off. Escalation rules are automatic failsafes.
Most Common Failure Mode
The most common failure in hierarchical plan approval is missing manager data. A participant has no manager field, or the manager field points to an inactive user, or the manager's manager is missing. The workflow hangs waiting for an approver that doesn't exist. This is discovered during UAT or in production, creating a support issue. Prevention: validate participant hierarchy completeness before go-live using a data audit query.
Pattern 2: Quota Dispute Resolution
Overview
A participant disputes their quota (either the value seems wrong, or the territory assignment is incorrect). The dispute is submitted through IM's dispute interface. A workflow routes the dispute to a compensation analyst for investigation. The analyst reviews the dispute and either approves an adjustment (quota is updated) or rejects the dispute (participant receives explanation). If the adjustment is large (>20% change), escalate to the finance director for additional review.
Configuration Walkthrough
Trigger Event: dispute.submitted
Conditions: None (all disputes should be processed).
Step 1 — Send Confirmation Notification:
- Notify participant that their dispute was received and is being reviewed.
- Provide dispute ID and expected resolution timeline (5 business days).
Step 2 — Route to Analyst (Value-Based):
// If dispute is for large adjustment, escalate to finance director
// Otherwise route to compensation analyst
def requestedAdjustment = dispute.newQuota - dispute.currentQuota
def percentChange = requestedAdjustment / dispute.currentQuota
if (percentChange > 0.20 || requestedAdjustment > 100000) {
workflow.requiresFinanceReview = true
return "FINANCE_DIRECTOR"
} else {
workflow.requiresFinanceReview = false
return "ANALYST"
}
Step 3a — Analyst Review (standard disputes):
- Assignee: compensation-analyst@company.com (or role-based lookup).
- SLA: 5 business days.
- Task body: Include current quota, requested adjustment, and justification from participant.
- On Approve: Advance to Step 4 (update quota).
- On Reject: Advance to Step 5 (send rejection notification).
Step 3b — Director Review (large disputes, conditional):
- Only execute if workflow.requiresFinanceReview = true.
- Assignee: finance-director@company.com.
- SLA: 3 business days.
- On Approve: Advance to Step 4 (update quota).
- On Reject: Advance to Step 5 (send rejection notification).
Step 4 — Update Quota (Automation):
This is the critical step. The quota update must be written back to IM. Two approaches:
Approach 1: REST API Call
// Call IM REST API to update participant quota
import groovy.json.JsonOutput
def updatePayload = [
participantId: participant.id,
territory: dispute.territory,
newQuota: dispute.newQuota,
effectiveDate: dispute.effectiveDate,
reason: "Dispute adjustment approved - " + workflow.approverComment
]
def apiUrl = "https://successfactors.com/api/v1/participants/${participant.id}/quota"
def response = httpClient.put(
url: apiUrl,
headers: [
"Authorization": "Bearer " + apiToken,
"Content-Type": "application/json"
],
body: JsonOutput.toJson(updatePayload)
)
if (response.status >= 400) {
log.error("Quota update failed: " + response.body)
workflow.quotaUpdateStatus = "FAILED"
return false
} else {
log.info("Quota updated for participant " + participant.id)
workflow.quotaUpdateStatus = "SUCCESS"
return true
}
Approach 2: Groovy Direct Update (if IM APIs allow)
// Direct update via IM object API
try {
participant.quota = dispute.newQuota
participant.territory = dispute.territory
participant.quotaEffectiveDate = dispute.effectiveDate
participant.save()
log.info("Quota updated successfully for " + participant.id)
return true
} catch (Exception e) {
log.error("Quota update failed: " + e.message)
return false
}
Step 5 — Send Resolution Notification:
- If approved: "Your dispute has been approved. Your quota has been updated to $${newQuota}."
- If rejected: "Your dispute was reviewed and rejected. Reason: ${rejectionReason}."
Edge Cases and Solutions
Edge Case 1: API Failure When Updating Quota
Symptom: Dispute is approved, quota update API call fails, workflow completes successfully but quota is not updated. Participant and system are out of sync.
Solution: Check API response status. If status >= 400, retry or escalate to manual review. Never mark the workflow as approved if the quota update fails.
Edge Case 2: Duplicate Disputes
Symptom: Participant submits the same dispute twice, both are approved, quota is updated twice.
Solution: Add a condition to check if a dispute for the same participant and territory was already approved within the last N days. Skip the new dispute if a duplicate is found.
Edge Case 3: Quota Changed Between Dispute Submission and Approval
Symptom: Participant disputes quota of $100K. Admin updates quota to $120K. Dispute is approved for the original $100K. Quota ends up at wrong value.
Solution: When updating quota, check if the current quota in IM matches the quota at the time the dispute was filed. If not, escalate to manual review.
Most Common Failure Mode
The most common failure is API integration: the REST call to update quota fails due to network issues, API timeouts, or authentication failures. The workflow completes but the quota is never updated. Prevention: implement retry logic with exponential backoff, and always check the API response status before marking the workflow as successful.
Pattern 3: Exception Incentive Approval
Overview
A calculation run completes. The system detects outlier results (a participant's commission is 3x the role average, or quota achievement is >200%). These outliers are flagged for manual review. A workflow routes the exception to a compensation analyst. The analyst can approve the result as-is, or override with a corrected amount. Once approved, the exception is resolved and the statement is published.
This pattern is challenging because "outlier" is subjective and plan-specific. A 200% quota achievement might be normal for some plan types and exceptional for others.
Configuration Walkthrough
Trigger Event: calculation.completed OR exception.detected (depending on whether IM flags exceptions during calculation or a post-calculation workflow flags them).
Conditions:
- Only trigger if an exception flag is set (isOutlier = true).
- Only trigger if exception severity is HIGH (value-based thresholds defined per plan type).
Step 1 — Groovy Analysis: Determine Exception Type and Severity
// Analyze the exception to determine type and severity
import groovy.json.JsonSlurper
def exceptionType = null
def severity = null
def analysisDetails = [:]
// Get peer statistics for the participant's role
def rolePeers = getRoleStatistics(participant.role, plan.period)
// Check for commission outliers
if (statement.commissionAmount > rolePeers.mean + (3 * rolePeers.stdDev)) {
exceptionType = "HIGH_COMMISSION"
severity = "HIGH"
analysisDetails.mean = rolePeers.mean
analysisDetails.stdDev = rolePeers.stdDev
analysisDetails.percentileRank = calculatePercentile(statement.commissionAmount, rolePeers)
}
// Check for achievement outliers
if (statement.quotaAchievement > 200) {
exceptionType = "EXTREME_ACHIEVEMENT"
severity = "HIGH"
}
// Check for negative outliers (commission much lower than expected)
if (statement.commissionAmount < rolePeers.mean - (2 * rolePeers.stdDev)) {
exceptionType = "LOW_COMMISSION"
severity = "MEDIUM"
}
workflow.exceptionType = exceptionType
workflow.severity = severity
workflow.analysisDetails = analysisDetails
return severity
Step 2 — Route to Analyst:
- Assignee: compensation-analyst@company.com (role-based).
- SLA: 3 business days.
- Task body: Include exception analysis details (comparison to role average, percentile rank, etc.) and request approval or override.
- On Approve: Advance to Step 3 (publish statement as-is).
- On Override: Advance to Step 4 (analyst enters corrected amount, then publish).
Step 3 — Publish Statement As-Is:
- Type: Automation (Groovy script).
- Action: Update statement.status = "PUBLISHED".
- Advance to Step 5 (notification).
Step 4 — Apply Manual Override (conditional):
- Type: Automation (Groovy script).
- Action: Update statement.commissionAmount = workflow.overrideAmount. Log the override reason and analyst comment.
- Advance to Step 5 (notification).
Step 5 — Send Notification to Participant:
- Subject: "Your Statement Has Been Reviewed and Is Now Available"
- Body: Commission amount, achievement %, link to view details in SuccessFactors.
Edge Cases and Solutions
Edge Case 1: Defining "Outlier" Per Plan Type
Symptom: Quota achievement of 150% is normal in one plan type (aggressive growth markets) but exceptional in another (stable accounts).
Solution: Store outlier thresholds per plan type in a configuration table. When flagging exceptions, look up the plan-specific thresholds. Make thresholds configurable so they can be adjusted based on business feedback.
Edge Case 2: Multiple Exceptions for Same Participant
Symptom: A participant has both a HIGH_COMMISSION and an EXTREME_ACHIEVEMENT exception in the same calculation.
Solution: Combine exceptions into a single workflow instance. Route to analyst once, with multiple exception types documented. Analyst approves all exceptions together.
Edge Case 3: Analyst Override Creates a New Outlier
Symptom: Analyst overrides exception from $10K to $5K, but $5K is now a low-commission outlier.
Solution: When analyst enters an override amount, validate it against outlier thresholds. If the override creates a new outlier, flag it for secondary review.
Most Common Failure Mode
The most common failure is under-defining "outlier". An exception rule seems clear in the business context ("flag commissions > 3x role average") but doesn't account for plan-type variations or seasonal patterns. The workflow flags too many normal exceptions, or misses real exceptions. Prevention: define outlier rules in collaboration with the compensation team and iterate based on UAT feedback.
Pattern 4: Automated Statement Release
Overview
A calculation run completes and all statements are generated. All exceptions (if any) have been approved. The system is ready to publish statements to participants. A workflow automatically checks that all approvals are complete, publishes all statements, and sends a notification to each participant. This is pure automation — no human gate.
This pattern is lower-risk than the others because it's fully deterministic. It either succeeds or fails, but there's no judgment call involved.
Configuration Walkthrough
Trigger Event: calculation.completed
Conditions:
- Only trigger if calculation.status = "COMPLETED".
- Only trigger if all exceptions have been approved (check exception queue for pending items).
Step 1 — Validate Calculation State (Groovy):
// Validate that the calculation is ready for statement release
// Check: no pending approvals, no errors, all participants processed
def pendingExceptionCount = getPendingExceptionCount(calculation.id)
def failedParticipantCount = getFailedParticipantCount(calculation.id)
def totalParticipantCount = getParticipantCount(calculation.period)
def processedCount = totalParticipantCount - failedParticipantCount
if (pendingExceptionCount > 0) {
log.warn("Cannot release: " + pendingExceptionCount + " exceptions pending")
return false
}
if (failedParticipantCount > 0 && (failedParticipantCount / totalParticipantCount) > 0.05) {
log.warn("Cannot release: > 5% of participants failed")
return false
}
log.info("Validation passed: " + processedCount + " participants ready")
return true
Step 2 — Publish Statements (Automation):
// Publish all statements for the period
// Update statement.status = "PUBLISHED" and set publishedDate = now()
def publishedCount = 0
def failedCount = 0
def statements = getUnpublishedStatements(calculation.period)
statements.each { statement ->
try {
statement.status = "PUBLISHED"
statement.publishedDate = new Date()
statement.save()
publishedCount++
} catch (Exception e) {
log.error("Failed to publish statement for " + statement.participantId + ": " + e.message)
failedCount++
}
}
log.info("Published " + publishedCount + " statements, failed: " + failedCount)
workflow.publishedCount = publishedCount
workflow.failedCount = failedCount
return failedCount == 0
Step 3 — Send Notifications to All Participants:
- Type: Notification (batch).
- Recipients: All participants with published statements in the period.
- Subject: "Your Q2 2026 Compensation Statement Is Ready"
- Body: Commission amount, achievement %, link to view in SuccessFactors, compensation team contact.
- Channel: Email + push notification in SuccessFactors.
Step 4 — Log Release Event (Automation):
- Type: Groovy script.
- Action: Write audit log entry: "Period Q2 2026 statements released. Count: [publishedCount]. Published by: [workflow.initiatedBy]. Time: [timestamp]."
- Workflow outcome: "COMPLETED".
Edge Cases and Solutions
Edge Case 1: Partial Failure (Some Statements Publish, Others Fail)
Symptom: 95% of statements publish successfully, but 5% fail due to data issues. Participant's don't know if their statement is ready.
Solution: The batch automation should not fail entirely. Log failed participants separately and notify the admin. Send statements only to participants who published successfully. Follow up with failed participants after issues are resolved.
Edge Case 2: Notification Delivery Failures
Symptom: Statement is published but email delivery fails for a participant. Participant doesn't know their statement is ready.
Solution: Implement retry logic for email delivery. If email fails, queue for retry (e.g., retry after 1 hour, then 4 hours, then next day). Log all delivery failures.
Edge Case 3: Too Many Pending Exceptions
Symptom: 200 exceptions are pending approval, and the release workflow is waiting. Participants don't get statements until all exceptions are approved.
Solution: This is not an edge case — it's a design decision. You can choose to: (A) require all exceptions to be approved before any statements release (safest), or (B) release statements for non-exception participants and hold exception participants' statements separately. Option B is more user-friendly but more complex.
Most Common Failure Mode
The most common failure is incomplete validation. The workflow releases statements but the calculation wasn't actually complete (some participants weren't processed due to data errors). Participants get statements for incomplete data. Prevention: make validation strict. Check not only that the calculation is marked complete, but also that the expected participant count matches the processed count.
Workflow Pattern Comparison Table
| Pattern | Trigger | Complexity | Human Gate | Most Common Failure |
|---|---|---|---|---|
| Hierarchical Approval | plan.submitted | Medium (multi-step approval, escalation) | Yes (multiple approvers) | Missing manager data; workflow hangs |
| Quota Dispute | dispute.submitted | High (API integration, conditional routing) | Yes (analyst review) | Quota update API fails; quota not updated |
| Exception Approval | exception.detected | High (statistical analysis, override logic) | Yes (analyst review) | Poor outlier definition; too many false positives |
| Automated Release | calculation.completed | Medium (batch operations, logging) | No (pure automation) | Incomplete validation; releases partial data |
Cross-Pattern Interactions
In a complete IM implementation, these patterns interact:
- Plan submitted → Hierarchical Approval workflow executes → Plan approved.
- Plan activated → Calculation triggered.
- Calculation completes → Exception Approval workflow identifies outliers → Analyst approves exceptions.
- Exceptions approved → Automated Statement Release workflow executes → Statements published.
- Statement published → Participant reads statement, disputes quota if needed → Quota Dispute workflow executes → Analyst approves adjustment → Quota updated.
Design your workflows to work together. Data from one workflow is often input to the next. For example, the Automated Statement Release workflow needs to know which exceptions were approved (output from Exception Approval workflow).
Key Takeaways
- Hierarchical Plan Approval is the most common pattern. Its main failure mode is missing hierarchy data. Implement robust fallback logic and validate hierarchy completeness before go-live.
- Quota Dispute Resolution requires API integration to update quota. The main failure mode is API failure. Implement error handling and retry logic.
- Exception Incentive Approval is complex because "outlier" is subjective. Collaborate with the compensation team to define thresholds per plan type.
- Automated Statement Release is the lowest-risk pattern because it's fully deterministic. Make validation strict to prevent releasing incomplete data.
- Workflows interact. Design them to work together and to share data between workflow instances.