---
title: User offboarding workflow example
description: Example workflow for user offboarding that reassigns approvals, roles, and ownership after deactivation
component: pingoneaic
page_id: pingoneaic:identity-governance:administration/example-user-offboarding-workflow
canonical_url: https://docs.pingidentity.com/pingoneaic/identity-governance/administration/example-user-offboarding-workflow.html
llms_txt: https://docs.pingidentity.com/pingoneaic/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
keywords: ["workflows", "use cases", "examples", "user offboarding", "deprovisioning"]
section_ids:
  assumptions: Assumptions
  example: Example
---

# User offboarding workflow example

In this example, an administrator creates a workflow that triggers a series of *offboarding* tasks when a user update occurs, such as a status, manager, or department change.

The offboarding tasks include:

* Setting up a replacement user ID for the inactive user. Depending on the case, the replacement user is the manager or former manager.

* Setting up a replacement user ID as a delegate for the inactive user.

* Replacing all instances of other users delegating to the inactive user with replacement user ID.

* Replacing the inactive user with the replacement user ID as an app owner.

* Replacing the inactive user with the replacement user ID as an entitlements owner.

* Replacing the inactive user with the replacement user ID as a role owner.

* Replacing the inactive user with the replacement user ID as an access request approver.

* Replacing the inactive user with the replacement user ID as a violations approver.

## Assumptions

* Human Resources confirms the user's change in status, manager, or department and has activated offboarding tasks to stakeholders.

## Example

![An example of an inactivated user workflow.](../_images/governance-workflow-example-user-inactivated.png)

* 1 The Script node reads the event user information, including manager data from the request object.

  > **Collapse: Click to display the Get Replacement User ID script**
  >
  > ```js
  > // Insert logic to set ID of user who will be replacing inactive user
  > logger.info("Getting ID of user who will be replacing inactive user.");
  >
  > var content = execution.getVariables();
  > var requestId = content.get('id');
  >
  > // Read event user information from request object
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   var previousUserObj = requestObj.request.common.blob.before;
  >   var currentUserObj = requestObj.request.common.blob.after;
  >   var userId = currentUserObj.id;
  >   var replacementId = null;
  >
  >   // Check current value of manager, or previous manager if not present, to find a replacement user ID
  >   if (currentUserObj && currentUserObj.manager) {
  >     replacementId = currentUserObj.manager.id;
  >   }
  >   else if (previousUserObj && previousUserObj.manager) {
  >     replacementId = previousUserObj.manager.id;
  >   }
  >
  >   execution.setVariable('userId', userId);
  >   execution.setVariable('replacementId', replacementId);
  > }
  > catch(e) {
  >   logger.info("Unable to get replacement user ID for inactive user " + userId);
  > }
  > ```

* 2 The Script node adds the replacement user as a delegate for the inactive user so that they can act on their tasks.

  > **Collapse: Click to display Set Replacement User as Inactive User script**
  >
  > ```js
  > // Adding the Replacement User as a delegate for the Inactive User so that they will be able to act on their tasks
  > var content = execution.getVariables();
  > var userId = content.get('userId');
  > var replacementId = content.get('replacementId');
  >
  > // Read event user information from request object
  > try {
  >   if (replacementId) {
  >     logger.info("Adding " + replacementId + " as inactive user " + userId + "'s delegate");
  >     var payload = { proxyIds: [ "managed/user/" + replacementId ]};
  >     var proxyUpdate = openidm.action('iga/governance/user/' + userId + '/set-proxy', 'POST', payload, {});
  >     logger.info("Added " + replacementId + " as a delegate for inactive user " + userId);
  >   }
  > }
  > catch(e) {
  >   logger.info("Unable to add delegate for inactive user " + userId);
  > }
  > ```

* 3 The Script node replaces all instances of other users delegating to the inactive user with the replacement user.

  > **Collapse: Click to display the Replace Inactive User as delegate script**
  >
  > ```js
  > // Replacing all instances of others delegating to the inactive user with replacement user
  > // Before script: User A and User B both delegate to inactive User
  > // After script: User A and User B both delegate to replacement User
  >
  > var content = execution.getVariables();
  > var userId = content.get('userId');
  > var replacementId = content.get('replacementId');
  >
  > try {
  >   if (replacementId) {
  >     logger.info("Replacing instances of users delegating to inactive user " + userId + " with " + replacementId);
  >
  >     // Get the list of users delegating to inactive user
  >     var inactiveUser = openidm.query("managed/alpha_user/" + userId + '/taskPrincipals', { _queryFilter: 'true' }, [ '_refResourceId' ]);
  >     var usersDelegatingToInactiveUser = inactiveUser.result;
  >
  >     // Set the payloads
  >     var removePayload = { proxyIds: [ "managed/user/" + userId ]};
  >     var addPayload = { proxyIds: [ "managed/user/" + replacementId ]};
  >
  >     // For each delegate, remove the inactive user and add the replacement user
  >     for (var i = 0; i < usersDelegatingToInactiveUser.length; i++) {
  >       var delegatingUserId = usersDelegatingToInactiveUser[i]._refResourceId;
  >       openidm.action("iga/governance/user/" + delegatingUserId + "/remove-proxy", "POST", removePayload, {});
  >       openidm.action("iga/governance/user/" + delegatingUserId + "/set-proxy", "POST", addPayload, {});
  >     }
  >     logger.info("Replaced instances of users delegating to inactive user " + userId + " with " + replacementId);
  >   }
  > }
  > catch(e) {
  >   logger.info("Unable to replace instances of users delegating to inactive user " + userId + " with " + replacementId);
  > }
  > ```

* 4 The Script node invokes the APIs and executes business logic.

  > **Collapse: Click to display the App Owner Replacement script**
  >
  > ```js
  > /*
  > Script nodes are used to invoke APIs or execute business logic.
  > You can invoke governance APIs or IDM APIs.
  > Learn more in https://docs.pingidentity.com/pingoneaic/identity-governance/administration/workflow-configure.html.
  >
  > Script nodes should return a single value and should have the
  > logic enclosed in a try-catch block.
  >
  > Example:
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   applicationId = requestObj.application.id;
  > }
  > catch (e) {
  >   failureReason = 'Validation failed: Error reading request with id ' + requestId;
  > }
  > */
  >
  > var content = execution.getVariables();
  >   var userId = content.get('userId');
  >   var replacementId = content.get('replacementId');
  >   var queryFilter =  {"_queryFilter": "true"}
  >   var removeBody = [];
  >   var addBody = [];
  >
  >   var applications = openidm.query('managed/alpha_user/' + userId + '/ownerOfApp', queryFilter, []);
  >
  >   for(var app of applications['result']){
  >     removeBody.push({
  >       "operation": "remove",
  >       "field": "/ownerOfApp",
  >       "value": {
  >           "_ref": app._ref,
  >           "_refProperties": {
  >               "_id": app._id,
  >               "_rev": app._rev
  >           },
  >           "_refResourceCollection": "managed/alpha_application",
  >           "_refResourceId": app._refResourceId
  >       }
  >   })
  >     addBody.push({
  >       "operation": "add",
  >       "field": "ownerOfApp/-",
  >       "value": {
  >           "_ref": app._ref,
  >           "_refProperties": {}
  >       }
  >     })
  >   }
  >   openidm.patch('managed/alpha_user/'+ userId, null, removeBody)
  >   openidm.patch('managed/alpha_user/'+ replacementId, null, addBody)
  > ```

* 5 The Script node replaces the entitlement owner.

  > **Collapse: Click to display the Entitlement Owner Replacement script**
  >
  > ```js
  > /*
  > Script nodes are used to invoke APIs or execute business logic.
  > You can invoke governance APIs or IDM APIs.
  > Learn more in https://docs.pingidentity.com/pingoneaic/identity-governance/administration/workflow-configure.html.
  >
  > Script nodes should return a single value and should have the
  > logic enclosed in a try-catch block.
  >
  > Example:
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   applicationId = requestObj.application.id;
  > }
  > catch (e) {
  >   failureReason = 'Validation failed: Error reading request with id ' + requestId;
  > }
  > */
  > var content = execution.getVariables();
  >   var userId = content.get('userId');
  >   var replacementId = content.get('replacementId');
  >   var targetFilter =  {"targetFilter": {
  >       "operator": "EQUALS",
  >       "operand": {
  >           "targetName": "entitlementOwner.id",
  >           "targetValue": userId
  >       }
  >   }}
  >
  >   var entitlements = openidm.action('iga/governance/resource/search', 'POST', targetFilter, {});
  >
  >   for(var entitlement of entitlements['result']){
  >     var body = openidm.action('iga/governance/resource/' + entitlement.id + '/glossary', 'GET', {}, {})
  >     body.entitlementOwner = "managed/user/" + replacementId;
  >     openidm.action('iga/governance/resource/' + entitlement.id + '/glossary', 'PUT', body, {})
  >   }
  > ```

* 6 The Script node replaces the role owner.

  > **Collapse: Click to display the Role Owner Replacement script**
  >
  > ```js
  > /*
  > Script nodes are used to invoke APIs or execute business logic.
  > You can invoke governance APIs or IDM APIs.
  > Learn more in https://docs.pingidentity.com/pingoneaic/identity-governance/administration/workflow-configure.html.
  >
  > Script nodes should return a single value and should have the
  > logic enclosed in a try-catch block.
  >
  > Example:
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   applicationId = requestObj.application.id;
  > }
  > catch (e) {
  >   failureReason = 'Validation failed: Error reading request with id ' + requestId;
  > }
  > */
  >
  > var content = execution.getVariables();
  >   var userId = content.get('userId');
  >   var replacementId = content.get('replacementId');
  >   var targetFilter =  {"targetFilter": {
  >       "operator": "EQUALS",
  >         "operand": {
  >             "targetName": "glossary.idx./role.roleOwner",
  >             "targetValue": "managed/user/"+ userId
  >       }
  >   }}
  >
  >   var results = openidm.action('iga/governance/catalog/search', 'POST', targetFilter, {});
  >
  >   for(var result of results['result']){
  >     var body = openidm.action('iga/governance/role/' + result.role.id + '/glossary', 'GET', {}, {})
  >     body.roleOwner = "managed/user/" + replacementId;
  >     openidm.action('iga/governance/role/' + result.role.id + '/glossary', 'PUT', body, {})
  >   }
  > ```

* 7 The Script node reassigns approvals.

  > **Collapse: Click to display the Reassign Approvals script**
  >
  > ```js
  > /*
  > Script nodes are used to invoke APIs or execute business logic.
  > You can invoke governance APIs or IDM APIs.
  > Learn more in https://docs.pingidentity.com/pingoneaic/identity-governance/administration/workflow-configure.html.
  >
  > Script nodes should return a single value and should have the
  > logic enclosed in a try-catch block.
  >
  > Example:
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   applicationId = requestObj.application.id;
  > }
  > catch (e) {
  >   failureReason = 'Validation failed: Error reading request with id ' + requestId;
  > }
  > */
  >
  > var content = execution.getVariables();
  > var userId = content.get('userId');
  > var replacementId = content.get('replacementId');
  > var targetFilter =  {
  >   "targetFilter": {
  >         "operator": "AND",
  >         "operand": [
  >             {
  >                 "operator": "EQUALS",
  >                 "operand": {
  >                     "targetName": "decision.actors.active.id",
  >                     "targetValue": "managed/user/" + userId
  >                 }
  >             },
  >             {
  >                 "operator": "EQUALS",
  >                 "operand": {
  >                     "targetName": "decision.status",
  >                     "targetValue": "in-progress"
  >                 }
  >             }
  >         ]
  >     }
  > }
  >
  > var results = null
  > var params = {
  >   "_pageSize": 100,
  >   "_pageNumber": 0
  > }
  > do{
  >   results= openidm.action('iga/governance/requests/search', 'POST', targetFilter, params);
  >
  >   for(var result of results['result']){
  >     var phaseName = null;
  >     var actors = [];
  >     for(var user of result.decision.actors.active){
  >       if(user.id === "managed/user/"+ userId){
  >         phaseName = user.phase;
  >         actors.push({"id": "managed/user/" + replacementId, "permissions": user.permissions})
  >       }
  >     }
  >     for(var user of result.decision.actors.active){
  >       if(user.phase === phaseName && user.id !== "managed/user/"+ userId){
  >         actors.push({"id": user.id, "permissions": user.permissions})
  >       }
  >     }
  >     var body = { "updatedActors": actors  };
  >     if(phaseName){
  >       openidm.action('/iga/governance/requests/' + result.id + '/phases/' + phaseName + '/reassign', 'POST', body, {})
  >     }
  >   }
  > }
  > while (results.result.length > 0)
  > ```

* 8 The Script node reassigns violations.

  > **Collapse: Click to display the Reassign Violations script**
  >
  > ```js
  > /*
  > Script nodes are used to invoke APIs or execute business logic.
  > You can invoke governance APIs or IDM APIs.
  > Learn more in https://docs.pingidentity.com/pingoneaic/identity-governance/administration/workflow-configure.html.
  >
  > Script nodes should return a single value and should have the
  > logic enclosed in a try-catch block.
  >
  > Example:
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   applicationId = requestObj.application.id;
  > }
  > catch (e) {
  >   failureReason = 'Validation failed: Error reading request with id ' + requestId;
  > }
  > */
  >
  > logger.info('Script Task 3');
  >
  > logger.info('User Task');
  >
  > var content = execution.getVariables();
  > var userId = content.get('userId');
  > var replacementId = content.get('replacementId');
  > var targetFilter =  {
  >   "targetFilter": {
  >         "operator": "AND",
  >         "operand": [
  >             {
  >                 "operator": "EQUALS",
  >                 "operand": {
  >                     "targetName": "decision.actors.active.id",
  >                     "targetValue": "managed/user/" + userId
  >                 }
  >             },
  >             {
  >                 "operator": "EQUALS",
  >                 "operand": {
  >                     "targetName": "decision.status",
  >                     "targetValue": "in-progress"
  >                 }
  >             }
  >         ]
  >     }
  > }
  > var results = null
  > var params = {
  >   "_pageSize": 10,
  >   "_pageNumber": 0
  > }
  > do{
  > results = openidm.action('iga/governance/violation/search', 'POST', targetFilter, params);
  > if(results['result'].length > 0){
  >     for(var result of results['result']){
  >       var phaseName = null;
  >       var actors = [];
  >       for(var user of result.decision.actors.active){
  >         if(user.id === "managed/user/"+ userId){
  >           phaseName = user.phase;
  >           actors.push({"id": "managed/user/" + replacementId, "permissions": user.permissions})
  >         }
  >       }
  >       for(var user of result.decision.actors.active){
  >         if(user.phase === phaseName && user.id !== "managed/user/"+ userId){
  >           actors.push({"id": user.id, "permissions": user.permissions})
  >         }
  >       }
  >       var body = { "updatedActors": actors  };
  >       if(phaseName){
  >         openidm.action('/iga/governance/violation/' + result.id + '/phases/' + phaseName + '/reassign', 'POST', body, {})
  >       }
  >     }
  >   }
  > }
  > while(results.result.length > 0)
  > ```

* 9 The Script node completes the process and sets the ID of the user, replacing the inactive user.

  > **Collapse: Click to display the Complete Process script**
  >
  > ```js
  > // Insert logic to set ID of user who will be replacing inactive user
  > logger.info("Completing request workflow.");
  >
  > var content = execution.getVariables();
  > var requestId = content.get('id');
  >
  > // Read event user information from request object
  > try {
  >   var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
  >   var decision = {'status': 'complete', 'decision': 'approved', 'outcome': 'fulfilled'};
  >   var queryParams = { '_action': 'update'};
  >   openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
  >   logger.info("Request " + requestId + " completed.");
  > }
  > catch(e) {
  >   logger.info("Error finalizing user inactive workflow")
  > }
  > ```

|   |                                                                                                                                                                                                                                                             |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Download the JSON file for this workflow [here](../_attachments/workflows/workflowUIUserCreateEventTwoRequestsWorkflowExample.json).Learn more about how to import or export workflows in [workflow editor canvas](workflow-configure.html#orch-ui-canvas). |
