Designing Cross-Interaction Survey Stitching for Measuring Cumulative Customer Experience

Designing Cross-Interaction Survey Stitching for Measuring Cumulative Customer Experience

What This Guide Covers

This guide details the architectural pattern for correlating disparate customer interactions (voice, digital, email) with post-interaction survey responses to calculate a unified Customer Effort Score (CES) or Net Promoter Score (NPS) across a customer journey. The end result is a normalized dataset where every survey response is linked to the specific interaction transcript, agent metadata, and historical context, enabling accurate root-cause analysis for negative sentiment.

Prerequisites, Roles & Licensing

  • Licensing:
    • Genesys Cloud CX: CX 2 or CX 3 license tier (required for Architect Survey blocks and Interaction Analytics).
    • Add-on: Speech Analytics (if analyzing voice transcripts) or Digital Analytics (if analyzing chat/email text).
  • Permissions:
    • Interaction > Survey > View and Interaction > Survey > Edit
    • Architect > Flow > View and Architect > Flow > Edit
    • Reporting > Report > View and Reporting > Report > Edit
  • External Dependencies:
    • A downstream data lake or BI tool (e.g., Snowflake, Tableau, Power BI) capable of joining large datasets.
    • Genesys Cloud Interaction Data Export (IDE) or custom API polling infrastructure.

The Implementation Deep-Dive

1. Establishing the Correlation Key Strategy

The fundamental challenge in cross-interaction stitching is that Genesys Cloud generates unique identifiers for different entities: interaction.id, call.id, chat.id, and survey.response.id. These IDs are not inherently linked in a parent-child relationship within the UI. To stitch them, you must propagate a stable, persistent identifier through every stage of the interaction lifecycle.

We use the Customer-ID (typically a CRM Account Number or External User ID) as the primary correlation key. If a Customer-ID is unavailable, we fall back to Phone-Number or Email-Address, normalized to a canonical format.

The Trap: Relying solely on interaction.id for stitching.
interaction.id is unique to a single session. If a customer calls, then chats, then emails, they generate three distinct interaction.id values. If you only stitch by interaction.id, you create siloed metrics. You cannot measure the “Cumulative Experience” because you cannot see that the same customer had three negative interactions in 24 hours. You must pivot the data model from “Interaction-Centric” to “Customer-Centric.”

Architect Configuration: Propagating the Key

In Genesys Cloud Architect, you must capture the Customer-ID as early as possible and pass it explicitly to the Survey block.

  1. Capture: Use a Get Customer Info block or an API block to retrieve the Customer-ID from your CRM or a local lookup table. Store this in a flow variable named cust_id.
  2. Sanitize: Ensure cust_id contains no special characters that might break URL encoding or SQL injection patterns if passed to external systems.
  3. Inject into Survey: When configuring the Survey block in Architect:
    • Go to the Survey configuration tab.
    • Under Custom Fields, map the variable cust_id to a custom field named x_cust_id.
    • Critical: Do not rely on the default Caller ID field. Caller IDs are often spoofed, masked, or inconsistent (e.g., +15551234567 vs (555) 123-4567).

Architect Expression Example:
If you are constructing the survey URL dynamically for a digital channel or an IVR SMS link, you must append the correlation key:

// Architect Expression for constructing a survey URL with tracking
"https://survey.genesys.cloud/respond?surveyId=" + surveyId + "&ref=" + cust_id

For voice channels, the x_cust_id custom field is embedded in the survey response payload sent to Genesys.

2. Configuring Survey Response Payloads via API

To stitch data outside of the Genesys UI, you must access the raw survey responses via the Genesys Cloud REST API. The default reporting tables often aggregate data too heavily for granular stitching.

We use the Survey Response API to retrieve individual responses. The key is to ensure the response object contains the x_cust_id custom field we injected in Architect.

HTTP Method: GET
Endpoint: /api/v2/surveys/responses

Query Parameters:

  • dateFrom: ISO 8601 timestamp (e.g., 2023-10-01T00:00:00.000Z)
  • dateTo: ISO 8601 timestamp
  • expand: customFields (This is mandatory. Without this, the API returns only the standard fields, and your x_cust_id will be missing.)

Sample Response Payload:

{
  "id": "response-uuid-123",
  "surveyId": "survey-uuid-456",
  "surveyName": "Post-Call CSAT",
  "responseDate": "2023-10-05T14:30:00.000Z",
  "interactionId": "interaction-uuid-789",
  "customFields": [
    {
      "name": "x_cust_id",
      "value": "CRM-ACC-998877"
    }
  ],
  "answers": [
    {
      "questionId": "q1",
      "questionName": "Overall Satisfaction",
      "answerValue": "2"
    }
  ]
}

The Trap: Ignoring the interactionId in the survey response.
The interactionId in the survey response is the bridge to the actual conversation. Many architects assume the survey response contains the transcript or agent details. It does not. The survey response is a lightweight record. You must join survey.response.interactionId to interaction.id in your data lake to retrieve the media, transcripts, and agent metadata.

3. Building the Interaction Data Join

Once you have the survey responses with x_cust_id and interactionId, you must pull the corresponding interaction data. This requires using the Interaction Data Export (IDE) or polling the Interaction API for detailed metadata.

Step 3a: Retrieving Interaction Metadata

Use the Interaction API to fetch the details of the specific interaction linked to the survey.

HTTP Method: GET
Endpoint: /api/v2/interactions/{interactionId}

Required OAuth Scope: interaction:view

Sample Interaction Payload (Relevant Fields):

{
  "id": "interaction-uuid-789",
  "type": "voice",
  "initiationDirection": "inbound",
  "customer": {
    "id": "customer-uuid-abc",
    "phone": "+15551234567",
    "email": "user@example.com"
  },
  "routing": {
    "queueId": "queue-uuid-xyz",
    "agentId": "agent-uuid-def",
    "wrapUpCode": "Resolved"
  },
  "media": [
    {
      "id": "media-uuid-123",
      "type": "voice",
      "start": "2023-10-05T14:00:00.000Z",
      "end": "2023-10-05T14:05:00.000Z"
    }
  ]
}

Step 3b: Normalizing the Customer Key

In your data transformation layer (e.g., dbt, Spark, or Python Pandas), you must normalize the customer identifier across both datasets.

  1. Extract x_cust_id from the Survey Response customFields array.
  2. Extract customer.phone or customer.email from the Interaction payload if x_cust_id was not captured (fallback scenario).
  3. Create a unified customer_key column.

The Trap: Mismatched Data Types.
The x_cust_id in the survey response is a string. The customer.id in the interaction API might be a UUID or a different string format. If your CRM passes 12345 as an integer in one system and "12345" as a string in another, joins will fail. Always cast all correlation keys to VARCHAR or STRING in the ETL process before joining.

4. Calculating Cumulative Metrics

With the joined dataset, you can now calculate cumulative metrics. The standard approach is to define a “Customer Journey Window” (e.g., 7 days, 30 days).

Metric Definition: Cumulative CES

Customer Effort Score (CES) is typically a 1-5 scale. A cumulative CES should not be a simple average. A single “5” (Very High Effort) after two "1"s (Very Low Effort) does not cancel out the frustration. We use a Weighted Recency Model.

Formula:
$$ Cumulative CES = \frac{\sum (Score_i \times Weight_i)}{\sum Weight_i} $$

Where $Weight_i = \frac{1}{TimeDifference_i + 1}$

This formula gives higher weight to more recent interactions. An interaction from today weighs more than an interaction from last week.

Implementation in SQL/BI Tool

Assuming you have a table survey_interaction_joined with columns: customer_key, survey_score, interaction_timestamp.

WITH interaction_weights AS (
    SELECT
        customer_key,
        survey_score,
        interaction_timestamp,
        -- Calculate days since the reference date (e.g., today)
        DATEDIFF(day, interaction_timestamp, CURRENT_DATE) as days_ago,
        -- Apply recency weight: 1 / (days_ago + 1)
        1.0 / (DATEDIFF(day, interaction_timestamp, CURRENT_DATE) + 1) as recency_weight
    FROM
        survey_interaction_joined
    WHERE
        interaction_timestamp >= DATEADD(day, -30, CURRENT_DATE) -- 30-day window
)
SELECT
    customer_key,
    -- Weighted Average Calculation
    SUM(survey_score * recency_weight) / SUM(recency_weight) as cumulative_ces,
    COUNT(*) as total_interactions_in_window
FROM
    interaction_weights
GROUP BY
    customer_key
HAVING
    total_interactions_in_window > 1 -- Only show customers with multiple interactions

The Trap: Dividing by Zero or Null Scores.
If a customer submits a survey but skips the scoring question, survey_score is NULL. In SQL, NULL * Weight is NULL. You must filter out NULL scores before aggregation, or use COALESCE(survey_score, 0) if your business logic dictates that non-responses are treated as neutral (rarely recommended).

Validation, Edge Cases & Troubleshooting

Edge Case 1: Multi-Channel Interaction Mismatch

The Failure Condition:
A customer calls (Voice), then follows up with a Chat. The Voice interaction has x_cust_id = “CRM-999”. The Chat interaction has x_cust_id = NULL because the chat widget did not capture the CRM ID before the survey was triggered.

The Root Cause:
The Chat Architect flow did not execute the “Get Customer Info” block before the Survey block, or the cookie/IDP session expired.

The Solution:
Implement a “Fallback Stitching” logic in your ETL.

  1. If x_cust_id is NULL in the survey response, look at the interaction.customer.email or interaction.customer.phone.
  2. Join the Survey Response to the Interaction via interactionId.
  3. Use the Interaction’s customer email/phone to join to the CRM master table to resolve the customer_key.
  4. Warning: This is less reliable due to email typos or shared family phone numbers. Prioritize the explicit x_cust_id capture in Architect.

Edge Case 2: Survey Response Lag and Data Freshness

The Failure Condition:
You run a daily report on Cumulative CES. The report shows a customer with a low CES, but the customer actually submitted a high CES survey 2 hours ago. The report is stale.

The Root Cause:
Survey responses are not always immediately available in the reporting tables. There is a processing latency (typically 15-30 minutes) between survey submission and API availability. Furthermore, if you are using Interaction Data Export (IDE), batches may be delayed.

The Solution:

  1. API Polling: For real-time dashboards, poll the /api/v2/surveys/responses endpoint every 5-10 minutes for the last 24 hours.
  2. Timestamp Filtering: Always filter survey responses by responseDate in the last N hours, not by interactionDate. A customer might interact at 10:00 AM but submit the survey at 2:00 PM. The “Cumulative Experience” should reflect the survey time, not the call time, for recency weighting.
  3. Idempotency: Ensure your ETL pipeline handles duplicate records. If you poll frequently, you may fetch the same survey response twice. Use survey.response.id as the unique key to upsert records.

Edge Case 3: Agent Wrap-Up Code Mismatch

The Failure Condition:
You attempt to correlate negative surveys with specific agent behaviors. You find that agents with high wrap-up rates still receive poor survey scores. The correlation is weak.

The Root Cause:
The wrapUpCode in the interaction payload is set by the agent at the end of the call. It is subjective. The survey score is set by the customer. They are not causally linked. However, if you are trying to stitch survey data to Speech Analytics sentiment scores, you must ensure the interactionId matches exactly.

The Solution:
Do not rely on Wrap-Up Codes for stitching. Use the interactionId to join Survey Data to Speech Analytics Transcripts.

  1. Fetch Speech Analytics results via /api/v2/analytics/conversations/details/query.
  2. Filter by interactionId = survey.response.interactionId.
  3. Compare sentiment.score from Speech Analytics with survey_score.
  4. This allows you to identify “Hidden Negatives”: Calls where the agent marked “Resolved” and Speech Analytics shows Neutral Sentiment, but the Customer gave a 1/5 score. These are your highest-priority coaching opportunities.

Official References