ISC Web Services Connector reuses requestEndPoint URL during batch AddEntitlement provisioning

Summary: During batch AddEntitlement provisioning via Identity Refresh, the Web Services Connector reuses the requestEndPoint URL from one operation for all subsequent operations in the batch, instead of constructing a unique URL per entitlement. This causes 409 “Already Exists” errors on target systems that require unique URLs per entitlement (e.g., JumpCloud’s /usergroups/{GROUP_ID}/members endpoint).

What is the correct behavior?

When multiple AddEntitlement operations are dispatched in a single provisioning plan, the connector should dynamically replace the URL path variable for each entitlement. For example:

  • Entitlement 1 → POST /usergroups/5f07cf84b544065386e22b5d/members
  • Entitlement 2 → POST /usergroups/62865991e8ab42000180ae83/members
  • Entitlement 3 → POST /usergroups/6287b3a1fa71fd0001e5ac28/members

Actual behavior: All 9 requests are sent to the same URL (e.g., /usergroups/62865991e8ab42000180ae83/members), causing the first call to succeed and subsequent calls to return 409 “Already Exists” because the user is already a member of that specific group.

What product feature is this related to?

The Web Services Connector’s batch provisioning logic when handling multiple AddEntitlement AttributeRequest objects in a single ProvisioningPlan. This specifically affects APIs that require entitlement-specific URL paths (not just body parameters).

What are the steps to reproduce the issue?

  1. Configure a Web Services Connector source against an API that requires per-entitlement URL paths (e.g., JumpCloud: POST /usergroups/{groupId}/members)
  2. Define the AddEntitlement operation with a URL path variable like $entitlement.id$
  3. Assign an identity multiple entitlements (e.g., via Access Profiles in a Role)
  4. Trigger an Identity Refresh that provisions multiple entitlements for the same identity
  5. Observe that all API calls use the same group ID in the URL, regardless of the attributeValue field in the provisioning plan

Evidence from event logs

The AddEntitlementFailure events in the ISC event log show the mismatch:

Field Value
attributeValue 5f07cf84b544065386e22b5d (correct target group)
URL in errors field /usergroups/62865991e8ab42000180ae83/members (wrong group — same URL for all 9 requests)

The user IS a member of all 9 target groups in the target system (confirmed via API). The 409 errors are caused by the connector sending all requests to the same (wrong) group URL, not by the target system rejecting valid requests.

Environment

  • Connector: Web Services (OOTB, no customization)
  • Rules: None — no BeforeOperationRule, no AfterOperationRule, no BeanShell
  • addRemoveEntInSingleReq: Not set (default)
  • Target system: JumpCloud (REST API)
  • Trigger: Identity Refresh (not manual access request)

What have we tried?

  • Verified the user’s actual group memberships via JumpCloud API — all correct
  • Confirmed no custom rules exist on the source
  • Reviewed addRemoveEntInSingleReq documentation — this parameter batches entitlements into a single JSON payload but does not fix URL path construction
  • Opened a support case (currently being deflected to Professional Services)

Related threads

Has anyone else experienced this behavior with the Web Services Connector when the target API requires unique URLs per entitlement? Is there a connector-level configuration fix, or is a BeforeOperationRule the only workaround?

Update: Root Cause Confirmed & Fix Verified in Production

After 5 days of production monitoring (2026-04-23 → 2026-04-28), I can confirm the root cause and a working fix for this issue. Sharing here so others who hit the same problem don’t have to spend weeks debugging.


Root Cause

The JumpCloud Web Services Connector’s user_group Add/Remove Entitlement operations used $request.user_group$ in the Context URL:

/v2/usergroups/$request.user_group$/members

The $request keyword is not in the documented list of supported Context URL placeholders. Per SailPoint’s documentation, the supported keywords are: plan, response, application, getobject, authenticate.

During batch provisioning (multiple AddEntitlement operations in a single provisioning plan), $request.user_group$ does not re-resolve per entitlement — it locks to the first value and reuses it for all subsequent API calls. This causes all requests to hit the same JumpCloud group URL, producing 409 Already Exists errors for all but the first group.

The tell-tale symptom: In your event logs, the attributeValue field (correct target group ID) does not match the group ID in the error URL. For example:

  • attributeValue: 5f91a0fc232e112cf0e1a434 (Slack group)

  • URL called: /v2/usergroups/65e527d501f44c000158a4a5/members (wrong group)


The Fix

Single keyword swap in the HTTP Operations Context URL — change $request to $plan:

Operation Before (broken) After (fixed)
Add Entitlement - user_group /v2/usergroups/$request.user_group$/members /v2/usergroups/$plan.user_group$/members
Remove Entitlement - user_group /v2/usergroups/$request.user_group$/members /v2/usergroups/$plan.user_group$/members

The $plan keyword correctly resolves per-item in batch operations. Notably, the active_directory entitlement operation on the same JumpCloud source already uses $plan.active_directory$ and works correctly — confirming this is a configuration inconsistency, not a platform limitation.


How to Apply the Fix

Step 1: Identify the Affected Operations

Pull your JumpCloud source configuration via the API:

bash

GET /v3/sources/{SOURCE_ID}

In the connectorAttributes.connectionParameters, find operations with contextUrl containing $request.:

json

{

“name”: “Add entitlement - user_group”,

“contextUrl”: “/v2/usergroups/$request.user_group$/members”,

}

Step 2: Apply the Fix via API PATCH

Use PATCH /v3/sources/{SOURCE_ID} to update the connectionParameters with corrected Context URLs. Replace $request.user_group$ with $plan.user_group$ in both the Add and Remove entitlement operations.

http

PATCH /v3/sources/{SOURCE_ID}

Content-Type: application/json-patch+json

[

{

“op”: “replace”,

“path”: “/connectorAttributes/connectionParameters”,

“value”: “”

}

]

:warning: Important: Back up your full source config before patching. The connectionParameters field contains all HTTP operations — you’re replacing the entire block, not just the URL.

Step 3: Validate in Sandbox First

If you have a sandbox environment, test the fix there before production:

  1. Apply the PATCH to your sandbox source

  2. Trigger a provisioning plan that assigns 3+ entitlements to a single identity

  3. Verify each ENTITLEMENT_ADD_PASSED event has a unique attributeValue

  4. Verify zero AddEntitlementFailure events

Step 4: Monitor in Production

After applying to production, monitor for 3-5 days:

  • Search events: name:"Add Entitlement Failed" — should be zero

  • Search events: name:"Add Entitlement Passed" — each batch should show unique group IDs


Production Validation Results

After 5 days of monitoring in our production tenant:

Metric Result
Entitlement adds (successful) 32
Entitlement removes (successful) 54
Failures 0
Batch operations tested 4 identities
Largest batch 16 concurrent entitlements → all unique URLs

The key validation was an Independent Contractor onboarding with 9 simultaneous group assignments — the identical scenario that previously produced 18 AddEntitlementFailure events. Post-fix: 9/9 passed with 9 unique attributeValue IDs, zero errors.


How to Detect If You’re Affected

  1. Search your event logs for AddEntitlementFailure events with 409 errors

  2. Compare the attributeValue field with the group ID in the error URL

  3. If they don’t match → you have this bug

  4. Pull your source config and check if any entitlement operation Context URLs use $request.*$ instead of $plan.*$


Key Takeaway

The $request keyword is undocumented for Context URL placeholders and does not iterate per-entitlement in batch provisioning plans. If your Web Services Connector target API requires unique URLs per entitlement (like JumpCloud’s /usergroups/{ID}/members), use $plan.*$ in the Context URL instead.

This is not a BeforeOperationRule fix — it’s a one-line configuration correction that aligns the Context URL with the documented placeholder keywords that the connector engine actually supports for per-item resolution.


Environment: ISC SaaS (prd07-useast1), Web Services Connector (OOTB, no custom rules)