Web Service Connector

@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