Web Service Connector

Hi Team,

I am working on Web Service Connector.

Test Connections, Account Aggregations and Group Aggregations are working fine.

I have one Attribute which is marked as Entitlement and Multivalued.

Create Account is working fine without Entitlement or with one Entitlement.

Issue: If I want to add more than one Entitlement, it is not working saying that only one entitlement is getting assigned not the other one.

Request Payload:

{
“firstName”: “$plan.firstName$”,
“lastName”: “$plan.lastName$”,
“authorised”: true,
“division”: {
“href”: " https://gmail.com"
},
@Email”: “$plan.email$”,
“accessGroups”: [
{
“accessGroup”: {
“href”: $plan.accessGroups$
}
}
]
}

How to resolve that issue?

@RAkhauri what is the error you are getting and have you defined entitlement types?

@RAkhauri This is a classic scenario where you need to determine whether your target system’s API supports entitlement assignment during the “Create Account” operation, or if it provides a separate endpoint specifically for managing entitlements.

When SailPoint triggers a Create Account operation (because no existing account is found on the target), the connector executes that operation only once. As a result, only one entitlement is typically included usually the one associated with the initial role or access profile that triggered provisioning. This explains why only a single entitlement gets assigned, even if multiple were requested.

If your target system supports a dedicated API endpoint for entitlement assignment, the best practice is to:

  1. Create a new HTTP Operation of type “Add Entitlement” in your Web Services connector.

  2. Configure its endpoint URL, headers, and request body according to the target system’s entitlement-assignment API.

  3. With this in place, the connector will automatically call the “Add Entitlement” operation multiple times, once for each entitlement being provisioned.

    For example, if three entitlements are requested, SailPoint will invoke the “Add Entitlement” operation three times, ensuring all entitlements are properly assigned on the target.

However, if your target system does not expose a separate entitlement management API, you can handle this through a Before Operation Rule on the “Create Account” operation. In that rule, you can use BeanShell or Java logic to:

  • Retrieve all entitlement values from the provisioning plan ($plan),

  • Construct the correct JSON payload that includes all required entitlements,

  • And set that payload back onto the Create request before it is executed.

That said, it’s important to consider how you will handle subsequent entitlement changes (adds or removals) in such a setup. If there’s no dedicated API endpoint for assigning or removing entitlements (e.g., accessGroup values), it becomes difficult to manage scenarios where:

  • A user needs additional access later, or

  • Existing access is revoked (e.g., via certification or manual removal).

Since the Create Account operation only runs once during the initial account creation, you would need “Add Entitlement” and “Remove Entitlement” operations configured for ongoing lifecycle updates on the access. Without these endpoints, entitlement updates after account creation cannot be properly handled by the connector.

Thanks,

Arshad.

Hi @Arshad

Thanks for your reply.

This is a classic scenario where you need to determine whether your target system’s API supports entitlement assignment during the “Create Account” operation, or if it provides a separate endpoint specifically for managing entitlements.

  • Yes, it supports Entitlement assignment during the “Create Account” operation.
  • Request body :
    {
    “firstName”: “$plan.firstName$”,
    “lastName”: “$plan.lastName$”,
    “authorised”: “$plan.authorised$”,
    “division”: {
    “href”: " https://edu.au:804/api/divisions/773"
    },
    @EmailEmail”: “$plan.email@AU”,
    @AU Cardholder ID”: “$plan.AU_Cardholder_@ProgramD$”,
    @Program Code”: “$plan.Pro@Academicram_Code$”,
    @Academic Plan Code”: “$plan.a@Facultyademic_plan_code$”,
    “@@Schoolaculty”: “$plan.facult@School$”,
    @School”: " $plan.school$"@Course
    @School Code”: “$plan.school_@EDUode$”,
    @Course Code”: “$plan.course_Code$”,
    “@ Person Affiliation”: “$plan.eduPersonAffiliation$”,
    “accessGroups”: [
    {
    “accessGroup”: {
    “href”: “$plan.accessGroup$”
    }
    }
    ]
    }

Note: “accessGroups” is marked as entitlement and multivalued.

Issue : If I will request for one Entitlement, it is working but if I will request for two entitlements, it is adding extra

Hence, it is saying bad request.

So if I will send multiple entitlements while doing the provisioning of an account, it will provision the user and only tag the first entitlement?

@RAkhauri -

You’ve defined an entitlement attribute such as accessGroup in your account schema, marked it as:

  • Entitlement: Marked

  • Multi-valued: Marked

And your Create Account JSON body looks like this:

{
  "firstName": "$plan.firstName$",
  "lastName": "$plan.lastName$",
  "authorised": "$plan.authorised$",
  "division": { "href": "https://edu.au:804/api/divisions/773" },
  "@Email": "$plan.email$",
  "accessGroups": [
    {
      "accessGroup": {
        "href": "$plan.accessGroup$"
      }
    }
  ]
}

When provisioning a user with one entitlement, everything works perfectly.
But when you try multiple entitlements, only the first one is processed—or worse, you get a Bad Request error.

Why It Happens

SailPoint ISC’s Web Service Connector does not automatically “fan out” multi-valued attributes when they’re nested inside an object.

So, when $plan.accessGroup$ contains multiple values, it doesn’t generate multiple objects under accessGroups[]. Instead, it attempts to stuff all values into the same node, leading to malformed JSON or ignored entries.

The Correct Approach

You need to intercept the request before it’s sent and dynamically build the JSON payload.

That’s exactly what the Web Service Before Operation Rule is designed for.

The WebServiceBeforeOperationRule Solution

Below is a fully compliant ISC rule that:

  • Reads all accessGroup values from the provisioning plan.

  • Builds a clean, nested accessGroups array.

  • Ensures no duplicate or blank entries.

  • Returns the updated requestEndPoint with proper jsonBody.

Rule: Build Multi-Entitlement JSON for Create Account

import java.util.*;
import connector.common.JsonUtil;
import connector.common.Util;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;

// Inputs: requestEndPoint, provisioningPlan, application, restClient, oldResponseMap
// Return: updated requestEndPoint

// ---- helper functions ----
String getAttr(ProvisioningPlan plan, String name) {
  if (plan == null) return null;
  for (AccountRequest accReq : Util.iterate(plan.getAccountRequests())) {
    if ("Create".equalsIgnoreCase(accReq.getOperation())) {
      for (ProvisioningPlan.AttributeRequest ar : Util.iterate(accReq.getAttributeRequests())) {
        if (name.equalsIgnoreCase(ar.getName())) {
          Object v = ar.getValue();
          return (v == null) ? null : String.valueOf(v).trim();
        }
      }
    }
  }
  return null;
}

List<String> getAttrList(ProvisioningPlan plan, String name) {
  List<String> values = new ArrayList<>();
  if (plan == null) return values;
  for (AccountRequest accReq : Util.iterate(plan.getAccountRequests())) {
    if ("Create".equalsIgnoreCase(accReq.getOperation())) {
      for (ProvisioningPlan.AttributeRequest ar : Util.iterate(accReq.getAttributeRequests())) {
        if (name.equalsIgnoreCase(ar.getName())) {
          Object v = ar.getValue();
          if (v instanceof Collection) {
            for (Object o : (Collection)v)
              if (o != null && !String.valueOf(o).trim().isEmpty())
                values.add(String.valueOf(o).trim());
          } else if (v != null) {
            values.add(String.valueOf(v).trim());
          }
        }
      }
    }
  }
  return values;
}

// ---- build JSON body ----
Map bodyMap = requestEndPoint.getBody();
if (bodyMap == null) bodyMap = new HashMap();

Map<String,Object> json = new LinkedHashMap<>();

json.put("firstName", getAttr(provisioningPlan, "firstName"));
json.put("lastName",  getAttr(provisioningPlan, "lastName"));
json.put("authorised", Boolean.valueOf(getAttr(provisioningPlan, "authorised")));
Map<String,Object> division = new LinkedHashMap<>();
division.put("href", "https://edu.au:804/api/divisions/773");
json.put("division", division);

json.put("@Email", getAttr(provisioningPlan, "email"));
json.put("@AU Cardholder ID", getAttr(provisioningPlan, "AU_Cardholder_ID"));
json.put("@Program Code", getAttr(provisioningPlan, "Program_Code"));
json.put("@Academic Plan Code", getAttr(provisioningPlan, "academic_plan_code"));
json.put("@Faculty", getAttr(provisioningPlan, "faculty"));
json.put("@School", getAttr(provisioningPlan, "school"));
json.put("@School Code", getAttr(provisioningPlan, "school_Code"));
json.put("@Course Code", getAttr(provisioningPlan, "course_Code"));
json.put("@EDU Person Affiliation", getAttr(provisioningPlan, "eduPersonAffiliation"));

// ---- accessGroups array ----
List<String> hrefs = getAttrList(provisioningPlan, "accessGroup");
Set<String> seen = new LinkedHashSet<>(hrefs);
List<Map<String,Object>> ag = new ArrayList<>();

for (String href : seen) {
  Map<String,Object> item = new LinkedHashMap<>();
  Map<String,Object> inner = new LinkedHashMap<>();
  inner.put("href", href);
  item.put("accessGroup", inner);
  ag.add(item);
}

if (!ag.isEmpty()) json.put("accessGroups", ag);

// Attach back to endpoint
String finalBody = JsonUtil.render(json);
bodyMap.put("jsonBody", finalBody);
requestEndPoint.setBody(bodyMap);
requestEndPoint.setFullUrl(requestEndPoint.getFullUrl().replaceAll("&&", "&"));

return requestEndPoint;

Configure it safely

  1. Remove any accessGroups block from the Create Account action’s static body template (to avoid duplicates).

  2. Attach this rule as the Before Rule for the Create Account HTTP action in your source. (Either via UI/VS Code or the PATCH API; the developer community threads explain where to set beforeRule / action index.) How to attach the rule

Expected Outcome

Single Entitlement — works as before
Multiple Entitlements — all entitlements get correctly assigned in a single request No Bad Request — payload is clean and API-compliant

Example final payload sent to API:

{
  "firstName": "John",
  "lastName": "Smith",
  "authorised": true,
  "division": { "href": "https://edu.au:804/api/divisions/773" },
  "@Email": "john.smith@edu.au",
  "accessGroups": [
    { "accessGroup": { "href": "https://edu.au/api/group/123" } },
    { "accessGroup": { "href": "https://edu.au/api/group/456" } }
  ]
}

Cheers!!!

1 Like

This definitely does solves the problem but only during account creation and that’s the exact same point I’ve mentioned above in my response as well.

However, the broader challenge here is that the “Add Entitlement” and “Remove Entitlement” operations don’t appear to be configured yet based on the author’s initial note, only aggregation-related operations are currently set up, and the focus is on the “Create Account” action.

While the BeanShell logic in the beforeOperation rule effectively handles multi-valued entitlements during account creation, it won’t cover subsequent entitlement changes as the create operation shall be executed only once, when the account is first provisioned and not for existing accounts.

A more scalable and maintainable approach would be to configure dedicated “Add Entitlement” and “Remove Entitlement” operations with their dedicated REST API endpoints within the Web Services connector configuration from the UI. This allows SailPoint to natively process entitlement additions and removals through standard provisioning events, rather than relying on custom logic in the create flow.

Thanks,

Arshad.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.