Implementing Custom Wrap-Up Code Selection Widgets with Cascading Category Hierarchies
What This Guide Covers
This guide details the architectural patterns, API contracts, and state management strategies required to build a custom UI widget that presents wrap-up codes in a cascading category hierarchy. You will implement a production-ready selection interface that fetches hierarchical code structures, enforces parent-child dependency validation, and posts the selected code to the active interaction lifecycle without blocking agent workflow.
Prerequisites, Roles & Licensing
- Licensing Tier: Genesys Cloud CX 2 or CX 3 (CX 1 does not support custom wrap-up code categories or advanced interaction lifecycle hooks). Custom UI development requires the Custom UI add-on or equivalent platform extension licensing.
- Granular Permissions:
Wrap-up Codes > View,Wrap-up Codes > Edit,Interactions > View,Interactions > Edit - OAuth Scopes:
wrapupcodes:view,interactions:wrapup,interactions:view - External Dependencies: Genesys Cloud OAuth 2.0 token exchange flow, active interaction context provider (Agent Desktop SDK or embedded iframe messaging bridge), HTTP/2 capable reverse proxy if routing through middleware
The Implementation Deep-Dive
1. Architectural Foundation & API Strategy
Wrap-up code hierarchies in Genesys Cloud are stored as a flat collection where each code references a parent category via the wrapupCodeCategoryId field. The platform does not natively return nested JSON structures. Your widget must reconstruct the hierarchy client-side while minimizing network latency and avoiding repeated API calls during active wrap-up windows.
Fetch the complete wrap-up code registry once per agent session. Caching the hierarchy in memory or sessionStorage prevents unnecessary load on the Genesys Cloud API gateway and eliminates render-blocking delays when an interaction enters the WRAP state. The initial fetch must occur during widget initialization, not during interaction state transitions.
API Contract: Retrieve Wrap-Up Code Registry
GET /api/v2/interactions/wrapupcodes
Authorization: Bearer <access_token>
Accept: application/json
Response Payload Structure (Truncated)
{
"pageSize": 50,
"pageNumber": 1,
"total": 124,
"entities": [
{
"id": "wrp-8a9b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p",
"name": "Payment Processed",
"description": "Transaction completed successfully",
"wrapupCodeCategoryId": "cat-1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
"categoryName": "Financial Transactions",
"version": 2,
"selfUri": "/api/v2/interactions/wrapupcodes/wrp-8a9b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p"
},
{
"id": "wrp-9b0c1d2e-3f4g-5h6i-7j8k-9l0m1n2o3p4q",
"name": "Refund Issued",
"description": "Full or partial refund processed",
"wrapupCodeCategoryId": "cat-1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
"categoryName": "Financial Transactions",
"version": 1,
"selfUri": "/api/v2/interactions/wrapupcodes/wrp-9b0c1d2e-3f4g-5h6i-7j8k-9l0m1n2o3p4q"
}
]
}
The Trap: Polling the wrap-up code endpoint on every interaction state change to WRAP. This pattern triggers rate limiting (429 Too Many Requests) under concurrent agent load and introduces 300-800ms latency before the agent can select a code. Genesys Cloud enforces strict API gateway throttling per tenant. A single agent polling every 5 seconds across a 500-seat deployment generates 100 requests per second, which exhausts standard tier limits and degrades interaction telemetry ingestion.
Architectural Reasoning: Reconstruct the hierarchy using a memoized mapping function during widget mount. Group entities by wrapupCodeCategoryId and sort categories alphabetically or by business priority. Store the structure as an adjacency list rather than a deeply nested object tree. Adjacency lists provide O(1) lookups for child codes when a parent category is selected, whereas nested trees require recursive traversal or complex pointer management. This approach also simplifies diffing when the registry updates mid-session.
const buildHierarchyMap = (rawCodes) => {
const categoryMap = new Map();
rawCodes.forEach(code => {
if (!categoryMap.has(code.wrapupCodeCategoryId)) {
categoryMap.set(code.wrapupCodeCategoryId, {
id: code.wrapupCodeCategoryId,
name: code.categoryName,
codes: []
});
}
categoryMap.get(code.wrapupCodeCategoryId).codes.push(code);
});
return Array.from(categoryMap.values()).sort((a, b) => a.name.localeCompare(b.name));
};
2. Cascading Hierarchy Implementation & State Management
Cascading selection requires strict state synchronization between the parent category dropdown and the child code dropdown. The child control must remain disabled until a valid parent is selected. When the parent changes, the child selection must reset to a neutral state, and the available options must filter to match the new parent ID.
Implement a controlled component pattern with explicit state variables: selectedCategoryId, selectedCodeId, isHierarchyLoading, and isSubmissionPending. Avoid uncontrolled inputs or library-managed state that obscures the validation pipeline. The UI must reflect the exact state of the underlying data model at all times.
State Transition Rules:
- Initial render:
selectedCategoryId=null,selectedCodeId=null, child dropdown disabled - Parent selection:
selectedCategoryIdupdates,selectedCodeIdresets tonull, child dropdown enables with filtered options - Child selection:
selectedCodeIdupdates, submission button enables - Parent change: Child resets, previous
selectedCodeIdclears, no orphaned state persists
The Trap: Allowing the child dropdown to retain its previous selection when the parent category changes. This creates a validation gap where the widget submits a code ID that belongs to a different category than the currently selected parent. The Genesys Cloud API will reject the payload with a 400 Bad Request if the code does not match the interaction’s allowed wrap-up configuration, but the UI will not reflect the mismatch until the error returns. This breaks agent workflow and increases handle time.
Architectural Reasoning: Enforce referential integrity at the UI layer before the request reaches the network. Filter the child options array synchronously when selectedCategoryId changes. Use a useMemo or equivalent memoization pattern to avoid recalculating the filtered list on every keystroke or unrelated state change. For large hierarchies (500+ codes), implement virtual scrolling or async search within the child dropdown. Rendering 500 DOM nodes in a standard <select> or custom dropdown causes layout thrashing and blocks the main thread. Virtualization renders only the visible viewport, reducing memory allocation and improving scroll performance on lower-end agent workstations.
const filteredCodes = useMemo(() => {
if (!selectedCategoryId) return [];
const category = hierarchyMap.find(c => c.id === selectedCategoryId);
return category ? category.codes : [];
}, [selectedCategoryId, hierarchyMap]);
const handleCategoryChange = (newCategoryId) => {
setSelectedCategoryId(newCategoryId);
setSelectedCodeId(null); // Hard reset prevents orphaned selections
setSubmissionEnabled(false);
};
3. Widget Integration & Interaction Lifecycle Binding
The widget must bind to the active interaction lifecycle to determine when wrap-up is permitted and to extract the correct interactionId. Genesys Cloud interactions transition through CONVERSATION, QUEUE, ACTIVE, WRAP, and COMPLETE. Wrap-up code submission is only valid during the WRAP state. Attempting to post a wrap-up code during ACTIVE or COMPLETE returns a 409 Conflict or 404 Not Found.
Subscribe to interaction state change events via the Agent Desktop SDK or iframe messaging bridge. Maintain a reference to the active interactionId and monitor the state field. Enable the submission control only when state === 'WRAP' and both selectedCategoryId and selectedCodeId are populated.
API Contract: Submit Wrap-Up Code
POST /api/v2/interactions/wrapupcodes/{wrapupCodeId}
Authorization: Bearer <access_token>
Content-Type: application/json
Request Payload
{
"interactionId": "int-3f4g5h6i-7j8k-9l0m-1n2o-3p4q5r6s7t8u"
}
The Trap: Ignoring the interaction timeout window and allowing the widget to remain open indefinitely. Genesys Cloud enforces a configurable wrap-up timeout (default 60-120 seconds). When the timeout expires, the platform automatically forces a wrap-up with a default code or no code, depending on tenant configuration. If the agent clicks submit after the timeout fires, the API returns 409 Conflict because the interaction has already transitioned to COMPLETE. The widget must listen for interaction:stateChange to COMPLETE and disable all controls immediately.
Architectural Reasoning: Decouple the UI submission logic from the network request using a pending state flag. Once the agent clicks submit, set isSubmissionPending = true, disable all inputs, and fire the POST request. This prevents double-submission from rapid clicks or network retries. Handle the response explicitly: on 204 No Content, clear state and emit a success event. On 4xx or 5xx, restore input state, display the specific error code, and allow retry. Never swallow HTTP errors silently. Genesys Cloud returns structured error payloads that indicate whether the failure is validation-based, lifecycle-based, or authentication-based.
const submitWrapUp = async () => {
if (!selectedCodeId || !activeInteractionId) return;
setSubmissionPending(true);
setError(null);
try {
const response = await fetch(`/api/v2/interactions/wrapupcodes/${selectedCodeId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${oauthToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ interactionId: activeInteractionId })
});
if (!response.ok) {
const errorBody = await response.json();
throw new Error(`API ${response.status}: ${errorBody.message || 'Submission failed'}`);
}
// 204 No Content expected on success
setSubmissionPending(false);
setWrapUpComplete(true);
resetSelectionState();
} catch (err) {
setSubmissionPending(false);
setError(err.message);
// Re-enable inputs for retry unless interaction is COMPLETE
if (currentInteractionState !== 'COMPLETE') {
setSubmissionEnabled(true);
}
}
};
Bind the widget to the platform’s event bus to receive real-time state updates. Polling GET /api/v2/interactions/{id} every 2 seconds is inefficient and drains battery on agent devices. Use WebSocket or Server-Sent Events (SSE) if available, or rely on the SDK’s onInteractionUpdate callback. The callback delivers delta updates containing only changed fields, reducing parsing overhead.
Validation, Edge Cases & Troubleshooting
Edge Case 1: Hierarchy Mismatch During Runtime
The failure condition: An administrator modifies, disables, or deletes a wrap-up code category while the agent is actively configuring the widget. The widget attempts to submit a code ID that no longer exists or has changed ownership.
The root cause: The client-side cache becomes stale. Genesys Cloud does not push real-time configuration updates to active sessions. The widget relies on a snapshot taken at initialization.
The solution: Implement a version-aware cache with a TTL (Time-To-Live) of 15-30 minutes. Before submission, validate the selected code ID against a lightweight integrity check. If the POST returns 404 Not Found, invalidate the cache, trigger a silent refetch of /api/v2/interactions/wrapupcodes, rebuild the hierarchy map, and display a non-blocking notification: “Wrap-up configuration updated. Please reselect.” Do not block the agent with a full page reload. Cache busting should occur on error, not on a fixed timer, to minimize unnecessary network traffic.
Edge Case 2: Race Condition on Wrap-Up Submission
The failure condition: The agent selects a code and clicks submit. The network request is delayed due to proxy latency or carrier handoff. The wrap-up timeout fires before the POST completes. The platform forces a default wrap-up. The delayed POST finally resolves with 409 Conflict. The widget displays a submission error, but the interaction is already complete.
The root cause: Asynchronous network operations compete with platform-enforced lifecycle timeouts. The widget lacks awareness of the remaining wrap-up window.
The solution: Query the interaction’s wrapUpTimeout value during initialization. Display a countdown timer in the widget header. If the remaining time drops below 5 seconds, automatically disable the submit button and switch to a read-only state. Implement request cancellation using AbortController. If the timeout window expires, abort any pending POST request and suppress error handling. Log the timeout event for analytics, but do not surface it to the agent. The platform already handled the fallback; the UI should reflect completion, not failure.
Edge Case 3: Multi-Interaction Context Collapse
The failure condition: An agent handles concurrent interactions (e.g., active call + active chat). The widget posts the wrap-up code to the wrong interactionId because the context provider defaults to the oldest or most recently opened interaction instead of the one currently in WRAP state.
The root cause: The widget maintains a single activeInteractionId variable without filtering by lifecycle state. Genesys Cloud allows multiple concurrent interactions per agent across different channels.
The solution: Maintain an interaction registry keyed by interactionId. Listen for state change events on all active interactions. Filter the registry to find exactly one interaction where state === 'WRAP' and channelType matches the widget’s scope. If multiple interactions enter WRAP simultaneously (rare but possible in blended channels), prioritize by startTimestamp or display a channel selector dropdown. Never assume a single active context. Validate the interactionId against the WRAP state immediately before submission. If no interaction matches, disable the submit control and show “No active wrap-up available.”
Edge Case 4: Category-Code Permission Mismatch
The failure condition: The agent has wrapupcodes:view but lacks queue-level or skill-level permissions to apply specific wrap-up codes. The UI allows selection, but the API returns 403 Forbidden.
The root cause: Wrap-up code visibility is global, but application permissions are scoped to routing configurations, queues, or interaction types. The widget fetches all codes but does not filter by agent entitlement.
The solution: Use the interaction context to determine allowed wrap-up codes. Genesys Cloud returns allowed codes in the interaction payload under wrapupCodes. Cross-reference the fetched hierarchy against the wrapupCodes array in the active interaction. Filter out codes not present in the allowed list. If the interaction payload does not include wrapupCodes, fall back to global visibility but log a telemetry warning. Never rely solely on the global registry for permission validation. The interaction object is the source of truth for runtime entitlements.