Deploying Proactive Web Chat Invitations for Cart Abandonment Recovery in Genesys Cloud CX

Deploying Proactive Web Chat Invitations for Cart Abandonment Recovery in Genesys Cloud CX

What This Guide Covers

You will configure a client-side detection mechanism for cart abandonment, trigger a proactive chat invitation via the Genesys Cloud Web Chat widget, and route the engagement to a dedicated recovery queue using Architect flow logic. The end result is a user experience where a visitor navigating away from the cart page receives a context-aware chat invitation offering assistance, with cart details seamlessly passed to the agent workspace.

Prerequisites, Roles & Licensing

  • Licensing: Genesys Cloud CX 2 or higher. Web Chat is included in CX 2. Advanced engagement analytics require the Engagement Add-on.
  • Permissions:
    • architect:flow:view and architect:flow:edit for flow configuration.
    • uc:ucsettings:view and uc:ucsettings:edit for Web Chat widget settings.
    • routing:queue:view and routing:queue:edit for queue creation.
  • OAuth Scopes: webchat:write, architect:flow:write, uc:ucsettings:write.
  • External Dependencies: Access to the client-side application codebase to inject the Genesys Cloud Web Chat SDK (@genesys/web-chat-sdk or genesyscloud-uc). A dedicated Queue configured for cart recovery agents.

The Implementation Deep-Dive

1. Client-Side Abandonment Detection and Event Emission

Architectural Reasoning:
Genesys Cloud cannot natively observe the state of a third-party e-commerce cart. The detection logic must reside in the client application. We deploy a client-triggered invitation pattern rather than a server-pushed pattern. Server-push requires continuous heartbeats to track user intent, which introduces latency and infrastructure overhead. Client-triggered invitations provide immediate signal propagation when the abandonment condition is met. The client emits a custom event to Architect, and the flow responds with an invitation token. This decouples the detection logic from the routing logic and allows the client to control the exact timing of the trigger based on business rules (e.g., cart value thresholds, item count).

Implementation:
Integrate the genesyscloud-uc library into the client application. The detection logic must monitor user navigation intent. The standard approach utilizes the visibilitychange event combined with a debounce timer. This avoids false positives caused by temporary tab switches while capturing genuine departure attempts.

The following JavaScript pattern implements the detection and event emission. This code assumes the Genesys Cloud Web Chat SDK is initialized and available as genesyscloud-uc.

import { GenesysCloudWebChatSDK } from '@genesys/web-chat-sdk';

// Configuration constants
const ABANDONMENT_DEBOUNCE_MS = 3000;
const CART_VALUE_THRESHOLD = 50.00;

let abandonmentTimer = null;
let isNavigatingAway = false;

// Helper to get cart state from your e-commerce platform
function getCartState() {
  // Replace with actual implementation accessing your cart store
  return window.YourEcommerceSDK.getCart();
}

// Event emitter function
function emitAbandonmentEvent(cartData) {
  if (!cartData || cartData.total < CART_VALUE_THRESHOLD) {
    return; // Skip low-value carts to reduce noise
  }

  // Send custom event to Architect
  // The 'data' payload is passed to the flow as interaction attributes
  genesyscloud-uc.sendEvent({
    name: 'cart_abandonment_trigger',
    data: {
      cartTotal: cartData.total,
      itemCount: cartData.items.length,
      cartId: cartData.id,
      userId: cartData.userId || 'guest',
      timestamp: new Date().toISOString()
    }
  }).catch((error) => {
    console.error('Failed to emit abandonment event:', error);
  });
}

// Visibility change handler
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    // User is leaving the tab or minimizing
    isNavigatingAway = true;
    
    // Start debounce timer
    abandonmentTimer = setTimeout(() => {
      if (isNavigatingAway) {
        const cartData = getCartState();
        emitAbandonmentEvent(cartData);
        // Reset state
        isNavigatingAway = false;
        abandonmentTimer = null;
      }
    }, ABANDONMENT_DEBOUNCE_MS);
  } else {
    // User returned to tab, cancel abandonment
    if (abandonmentTimer) {
      clearTimeout(abandonmentTimer);
      abandonmentTimer = null;
    }
    isNavigatingAway = false;
  }
});

// Handle back button and hard navigation
window.addEventListener('beforeunload', () => {
  if (isNavigatingAway) return; // Prevent double trigger
  
  // Note: beforeunload is synchronous. Async calls may not complete.
  // We rely on visibilitychange for the primary trigger.
  // This is a fallback for rapid closures where visibilitychange might race.
  const cartData = getCartState();
  if (cartData && cartData.total >= CART_VALUE_THRESHOLD) {
    // Use navigator.sendBeacon for reliable delivery during unload
    // Construct payload matching the custom event schema
    const payload = JSON.stringify({
      name: 'cart_abandonment_trigger',
      data: cartData
    });
    
    // Send to Genesys Cloud Web Chat event endpoint if SDK sendEvent is not available in sync context
    // Alternatively, ensure SDK sendEvent supports sync fallback or use sendBeacon to a middleware
    if (navigator.sendBeacon) {
      // This requires a custom endpoint or middleware if direct SDK sync is unavailable
      // Best practice: Rely on visibilitychange debounce as the primary mechanism
    }
  }
});

The Trap:
Developers frequently implement detection using the beforeunload event with asynchronous fetch calls or genesyscloud-uc.sendEvent. The beforeunload event handler must execute synchronously. Modern browsers throttle or abort asynchronous network requests initiated during beforeunload. If the event emission relies on a Promise resolution, the request fails silently, and the invitation never triggers. The debounce pattern on visibilitychange mitigates this by allowing the browser to process the event while the user is still technically in the context window, ensuring the network request completes. Additionally, failing to implement a debounce timer causes invitations to fire on every temporary tab switch, creating a spammy user experience and flooding the queue with abandoned interactions.

2. Architect Flow Configuration for Invitation Orchestration

Architectical Reasoning:
The Architect flow receives the custom event and determines whether to generate an invitation. The flow must enforce business rules, including business hours, queue capacity, and agent availability. We use the invite action to generate the invitation. The flow must map the cart data from the event payload to interaction attributes so the agent receives the context. The flow also serves as the control plane for invitation decay; if the user does not accept the invitation within a defined window, the interaction must be terminated to prevent queue clutter.

Implementation:
Create a new Flow in Architect. Configure the trigger to listen for the custom event name defined in the client code. Add actions to set attributes, check availability, and send the invitation.

{
  "id": "flow-cart-abandonment-id",
  "name": "Flow - Cart Abandonment Invitation",
  "description": "Triggers proactive chat invitation on cart abandonment event",
  "type": "webchat",
  "outcomes": [],
  "actions": [
    {
      "id": "set-cart-attributes",
      "type": "set_attributes",
      "label": "Set Cart Attributes",
      "settings": {
        "interaction": {
          "attributes": [
            {
              "name": "cartTotal",
              "value": "{{trigger.cartTotal}}"
            },
            {
              "name": "itemCount",
              "value": "{{trigger.itemCount}}"
            },
            {
              "name": "cartId",
              "value": "{{trigger.cartId}}"
            }
          ]
        },
        "queue": {
          "attributes": [
            {
              "name": "recoveryContext",
              "value": "Cart Value: {{trigger.cartTotal}} | Items: {{trigger.itemCount}}"
            }
          ]
        }
      }
    },
    {
      "id": "check-business-hours",
      "type": "split",
      "label": "Check Business Hours",
      "settings": {
        "conditions": [
          {
            "label": "Outside Business Hours",
            "expression": {
              "function": "isBusinessHours",
              "args": ["{{schedule.recovery-hours}}", false]
            },
            "goto": "action-end-flow"
          }
        ]
      }
    },
    {
      "id": "set-recovery-queue",
      "type": "set_queue",
      "label": "Set Recovery Queue",
      "settings": {
        "queue": {
          "id": "{{queue.recovery-queue-id}}"
        },
        "wrapUpCode": null,
        "wrapUpCodeRequired": false
      }
    },
    {
      "id": "send-invitation",
      "type": "invite",
      "label": "Send Proactive Invitation",
      "settings": {
        "message": "Hi there! We noticed you left items in your cart. Would you like help completing your purchase?",
        "timeout": 120,
        "queue": {
          "id": "{{queue.recovery-queue-id}}"
        },
        "attributes": {
          "source": "proactive_cart_abandonment"
        }
      }
    },
    {
      "id": "action-end-flow",
      "type": "end",
      "label": "End Flow",
      "settings": {
        "result": "success"
      }
    }
  ],
  "triggers": [
    {
      "type": "custom_event",
      "label": "Cart Abandonment Trigger",
      "settings": {
        "event": "cart_abandonment_trigger",
        "goto": "set-cart-attributes"
      }
    }
  ]
}

The Trap:
Configuring the invite action without validating queue capacity or agent availability leads to “zombie” invitations. If the recovery queue has no available agents, the invitation is generated and displayed to the user. When the user accepts, the interaction enters the queue and waits. If no agents become available, the interaction eventually times out, resulting in a lost conversion and degraded customer sentiment. The user sees an invitation, engages, and receives a “No agents available” error. To prevent this, insert a get_queue action before the invite action to check agentAvailabilities and queueCapacity. Only proceed to invite if agentAvailabilities > 0. Alternatively, configure the invite action with a strict timeout and ensure the client-side widget handles invitation rejection gracefully. A secondary trap involves mapping PII in the event data to queue attributes without redaction. Queue attributes are visible in analytics and may be logged. Ensure sensitive data like userId or payment tokens are stripped or masked in the flow using set_attributes with redaction rules or by excluding them from the payload entirely.

3. Web Chat Widget Configuration and Context Handling

Architectural Reasoning:
The Web Chat widget must be configured to accept and display proactive invitations. The widget settings control the invitation timeout, the UI behavior when an invitation is received, and the retry logic. We configure the widget to suppress the standard message input until an invitation is accepted, ensuring the user cannot bypass the proactive flow. The widget also manages the invitation lifecycle; if the user dismisses the invitation, the widget must clear the state to prevent re-triggering. Context passing relies on the genesyscloud-uc.sendEvent data payload, which the flow maps to interaction attributes. The agent workspace reads these attributes to display the cart summary.

Implementation:
Update the Web Chat widget configuration object. Enable invitations and set the timeout to match the flow invitation timeout. Configure the showInvitations flag and define the invitation UI customization.

const widgetConfig = {
  organizationId: 'your-org-id',
  deploymentId: 'your-deployment-id',
  environmentUrl: 'https://api.mypurecloud.com',
  locale: 'en-US',
  
  // Invitation Configuration
  invitations: {
    showInvitations: true,
    invitationTimeout: 120, // Must match flow invite timeout
    invitationDecay: true, // Automatically dismiss invitation after timeout
    maxInvitations: 1, // Prevent multiple concurrent invitations
    invitationMessage: {
      enabled: true,
      text: "Need help with your cart?"
    }
  },
  
  // Input Control
  input: {
    // Disable input until invitation is accepted or chat is established
    // This is handled by the flow state, but widget config can enforce defaults
    placeholder: "Waiting for agent invitation...",
    disabled: true // Initial state; flow enables input on chat start
  },
  
  // Custom Event Handling
  events: {
    onEvent: (event) => {
      console.log('Web Chat Event:', event);
      // Handle invitation acceptance/rejection callbacks if needed
    }
  },
  
  // Context and Attributes
  attributes: {
    // Static attributes passed with every interaction
    defaultAttributes: {
      channel: 'web_chat',
      campaign: 'cart_recovery'
    }
  }
};

// Initialize SDK
const sdk = new GenesysCloudWebChatSDK(widgetConfig);
sdk.start();

The Trap:
Mismatched timeout values between the widget invitationTimeout and the flow invite action timeout cause state desynchronization. If the widget timeout is shorter than the flow timeout, the widget dismisses the invitation UI while the flow still considers the invitation active. The user cannot accept the invitation, and the flow eventually times out, creating an orphaned interaction. If the flow timeout is shorter, the flow terminates the interaction while the widget still displays the invitation button. The user clicks accept, and the widget throws an error because the interaction no longer exists. Always align these values. Another trap involves the input.disabled configuration. If the input is disabled globally, users cannot type even after the invitation is accepted. The input state must be managed dynamically. The flow should send a set_attributes action or the widget should listen for the chat.connected event to re-enable the input. Hardcoding disabled: true without a re-enable mechanism locks the user out of the chat.

4. Agent Workspace Context Integration

Architectural Reasoning:
The agent requires visibility into the cart details to provide effective recovery assistance. The cart data passed via the custom event is mapped to interaction attributes in the flow. These attributes are accessible in the Agent Workspace via the Interaction Summary panel. We configure the Workspace layout to expose the custom attributes cartTotal, itemCount, and cartId. This allows the agent to view the cart value and potentially retrieve the cart state via an API call to the e-commerce platform using the cartId. This integration bridges the gap between the contact center and the commerce platform, enabling personalized recovery actions.

Implementation:
In Genesys Cloud Admin, navigate to Administration > User Interface > Workspace. Edit the Chat workspace layout. Add a custom component or modify the Interaction Summary to display the attributes cartTotal, itemCount, and cartId. Ensure the attributes are mapped to visible fields.

Additionally, configure a dynamic content block or a custom button in the workspace that allows the agent to fetch the full cart details. This requires a secure API integration. The agent clicks the button, which triggers a backend service to query the e-commerce API using the cartId from the interaction attributes. The result is displayed in a side panel. This avoids exposing full cart details in the initial interaction attributes, reducing PII exposure and payload size.

The Trap:
Displaying sensitive cart data directly in the interaction summary without access controls exposes PII to all agents with chat permissions. If the recovery queue includes junior agents or shared pools, they may see data for carts they should not access. Implement attribute-based access controls or mask sensitive attributes in the workspace for agents without specific roles. Another trap involves relying on the cartId for data retrieval without validating the cart state. Carts can expire or be purchased by the user after the invitation is generated. The agent workspace integration must handle API errors and cart not found states gracefully. If the cart is invalid, the agent receives an error, which disrupts the workflow. Implement a fallback UI state that indicates “Cart data unavailable” and prompts the agent to request details from the user.

Validation, Edge Cases & Troubleshooting

Edge Case 1: Race Condition on Invitation Acceptance

  • The Failure Condition: The user accepts the invitation, but an agent becomes unavailable or the queue capacity is reached between the invitation generation and the acceptance click. The widget displays “No agents available” or the connection fails.
  • The Root Cause: The invite action generates a pending interaction. The flow does not guarantee agent assignment at the time of invitation. If all agents wrap up or go offline during the invitation window, the interaction has no target.
  • The Solution: Configure the invite action with a queue that has overflow routing. If the recovery queue is full, overflow to a general support queue. Alternatively, implement client-side logic to detect invitation rejection and retry with a fallback queue. Monitor the invite action metrics in Analytics to track invitation success rates. If the success rate drops below 90%, investigate queue staffing or capacity constraints.

Edge Case 2: Multiple Invitations from Rapid Navigation

  • The Failure Condition: The user clicks the back button, triggers abandonment, then immediately navigates to another page and triggers abandonment again. Two custom events are emitted, resulting in two concurrent invitations.
  • The Root Cause: The client-side detection logic emits events without idempotency checks. The debounce timer resets on each visibility change, but rapid navigation can cause overlapping triggers if the debounce window is not strictly enforced.
  • The Solution: Implement an idempotency key in the event payload, such as a timestamp or session ID. Configure the flow to check for recent duplicate events using a split action that compares the current event timestamp against a stored attribute. If the difference is less than a threshold (e.g., 5 seconds), discard the duplicate event. Alternatively, configure the widget maxInvitations to 1 and ensure the flow checks for existing active interactions for the same user session before generating a new invitation.

Edge Case 3: Browser Back Button and History API Interference

  • The Failure Condition: The user clicks the browser back button. The visibilitychange event fires, triggering abandonment. However, the user is actually navigating to a previous page within the site, not leaving. The invitation appears on the previous page, which is irrelevant.
  • The Root Cause: Single-page applications (SPAs) using the History API may trigger visibilitychange or beforeunload on internal navigation if not handled correctly. The detection logic does not distinguish between internal navigation and actual departure.
  • The Solution: Integrate the detection logic with the SPA router. Listen for router navigation events. If the navigation is internal (e.g., to the product page or home), suppress the abandonment trigger. Only emit the event if the navigation target is external or if the user is closing the tab. Use popstate events to detect back button presses and validate the target URL. If the target is an internal route, cancel the abandonment timer.

Official References