How to Implement Customer Lookup via Architect GetExternalContactAction

How to Implement Customer Lookup via Architect GetExternalContactAction

What You Will Build

  • You will configure a Genesys Cloud Architect flow that triggers an external HTTP request to your backend service to retrieve customer data based on the caller’s phone number.
  • You will use the GetExternalContactAction node to map the inbound caller ID to a specific API endpoint and parse the JSON response into flow variables.
  • You will implement a Python Flask microservice that accepts the request, queries a mock database, and returns the customer profile in the format expected by Genesys Cloud.

Prerequisites

  • Genesys Cloud Account: Access to the Architect tool with permissions to create and publish flows.
  • Python 3.9+: Installed with pip for the backend service.
  • Dependencies: flask, requests for the Python backend.
  • Genesys Cloud SDK (Optional for backend): This tutorial focuses on the raw HTTP interaction for the backend, but you may use the genesys-cloud-purecloud-platform-client if you need to update the contact record after lookup.
  • Public Endpoint: Your backend service must be accessible via HTTPS from the Genesys Cloud data centers. For local testing, use a tunneling service like ngrok.

Authentication Setup

The GetExternalContactAction in Genesys Cloud Architect does not handle OAuth token exchange for your backend service automatically in the standard configuration. You must decide how to authenticate the request coming from Genesys Cloud to your service.

The most common pattern is using a shared secret (API Key) or a simple Bearer token. Below is the setup for a simple Bearer token validation in your Python backend.

# backend_app.py
import os
from flask import Flask, request, jsonify

app = Flask(__name__)

# In production, load this from environment variables
SECRET_TOKEN = "your_secure_secret_token_here"

def authenticate_request():
    """
    Validates the Bearer token sent by Genesys Cloud Architect.
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        return False
    
    # Expected format: Bearer <token>
    parts = auth_header.split()
    if len(parts) != 2 or parts[0] != "Bearer":
        return False
    
    return parts[1] == SECRET_TOKEN

@app.route("/api/customer/lookup", methods=["POST"])
def lookup_customer():
    if not authenticate_request():
        return jsonify({"error": "Unauthorized"}), 401
    
    # Logic continues in Implementation section
    pass

In Genesys Cloud Architect, you will configure the GetExternalContactAction to send this token in the Authorization header.

Implementation

Step 1: Configure the Backend Service

Your backend service must accept a POST request containing the caller’s phone number and return a JSON object with customer details. Genesys Cloud expects the response to map to a specific schema.

Create a simple database lookup. For this tutorial, we use a static dictionary.

# backend_app.py (continued)

# Mock database
CUSTOMER_DB = {
    "+15551234567": {
        "customerId": "CUST-001",
        "firstName": "Alice",
        "lastName": "Smith",
        "tier": "Gold",
        "lastInteractionDate": "2023-10-15"
    },
    "+15559876543": {
        "customerId": "CUST-002",
        "firstName": "Bob",
        "lastName": "Jones",
        "tier": "Silver",
        "lastInteractionDate": "2023-11-01"
    }
}

@app.route("/api/customer/lookup", methods=["POST"])
def lookup_customer():
    if not authenticate_request():
        return jsonify({"error": "Unauthorized"}), 401

    # Parse the incoming JSON body
    data = request.get_json()
    if not data or "phoneNumber" not in data:
        return jsonify({"error": "Missing phoneNumber field"}), 400

    phone_number = data["phoneNumber"]
    
    # Normalize phone number if necessary (e.g., remove spaces/dashes)
    # For this example, we assume exact match with E.164 format
    
    customer = CUSTOMER_DB.get(phone_number)
    
    if customer:
        # Return success with customer data
        response_data = {
            "status": "success",
            "data": customer
        }
        return jsonify(response_data), 200
    else:
        # Return not found
        response_data = {
            "status": "not_found",
            "data": None
        }
        return jsonify(response_data), 200 # Return 200, not 404, to avoid flow error handling complexity unless desired

Run this service locally:

export FLASK_APP=backend_app.py
flask run --port 5000

Use ngrok to expose this locally running server to the internet:

ngrok http 5000

Copy the https://...ngrok.io URL. You will use this in the next step.

Step 2: Configure the GetExternalContactAction in Architect

  1. Open Genesys Cloud and navigate to Admin > Architect.
  2. Create a new flow or open an existing IVR/ACD flow.
  3. Search for the node type Get External Contact Action. Add it to your flow canvas.
  4. Connect it to your entry point (e.g., after the Greeting node).

Node Configuration

In the node configuration panel, you must define the HTTP request details.

General Settings:

  • Name: Customer Lookup
  • Description: Retrieves customer profile based on caller ID

Request Configuration:

  • Method: POST
  • URL: https://your-ngrok-url.ngrok.io/api/customer/lookup
  • Headers: Add a new header.
    • Key: Authorization
    • Value: Bearer your_secure_secret_token_here
  • Content Type: application/json

Request Body:
You must construct the JSON payload that sends the caller’s phone number to your backend. Use the dynamic variable for the caller’s phone number.

{
  "phoneNumber": "{{contact.attributes.caller.phoneNumber}}"
}

Note: The variable {{contact.attributes.caller.phoneNumber}} contains the E.164 formatted phone number of the inbound caller. Ensure your backend expects this exact format.

Response Mapping:
This is the critical step. You must map the JSON response from your backend to Genesys Cloud flow variables.

In the Response Mapping tab, add the following mappings:

Response Field Flow Variable Type
status customerLookup.status String
data.customerId customerLookup.customerId String
data.firstName customerLookup.firstName String
data.lastName customerLookup.lastName String
data.tier customerLookup.tier String

Error Handling:

  • On Error: Connect this port to an error handling node (e.g., a Play Prompt saying “We are experiencing technical difficulties” followed by a Transfer to Agent).
  • Timeout: Set to 3000 ms (3 seconds). External lookups should be fast.

Step 3: Process Results and Route the Call

After the GetExternalContactAction node, add a Decision node to check if the customer was found.

  1. Decision Condition:

    • Condition: customerLookup.status Equals success
    • True Path: Connect to a “VIP Customer” path.
    • False Path: Connect to a “General Queue” path.
  2. VIP Customer Path:

    • Add a Play Prompt node.
    • Text: Hello {{customerLookup.firstName}}, welcome back. We see you are a {{customerLookup.tier}} tier member.
    • Connect to a Select Queue node for a specialized VIP queue.
  3. General Queue Path:

    • Add a Play Prompt node.
    • Text: Thank you for calling. Please hold for the next available agent.
    • Connect to a Select Queue node for the general support queue.

Publish the Flow:
Save and publish the flow. Ensure the flow is enabled and assigned to a Routing User or Application that matches the inbound call’s configuration.

Complete Working Example

Backend Service (Python/Flask)

Save this as customer_lookup_service.py.

import os
from flask import Flask, request, jsonify

app = Flask(__name__)

# Configuration
SECRET_TOKEN = os.getenv("LOOKUP_SECRET", "default_secret_change_me")

# Mock Database
CUSTOMER_DB = {
    "+15551234567": {
        "customerId": "CUST-001",
        "firstName": "Alice",
        "lastName": "Smith",
        "tier": "Gold",
        "lastInteractionDate": "2023-10-15"
    },
    "+15559876543": {
        "customerId": "CUST-002",
        "firstName": "Bob",
        "lastName": "Jones",
        "tier": "Silver",
        "lastInteractionDate": "2023-11-01"
    }
}

def authenticate_request():
    """
    Validates the Bearer token sent by Genesys Cloud Architect.
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        return False
    
    parts = auth_header.split()
    if len(parts) != 2 or parts[0] != "Bearer":
        return False
    
    return parts[1] == SECRET_TOKEN

@app.route("/api/customer/lookup", methods=["POST"])
def lookup_customer():
    """
    Endpoint called by Genesys Cloud GetExternalContactAction.
    """
    # 1. Authenticate
    if not authenticate_request():
        return jsonify({"error": "Unauthorized"}), 401

    # 2. Parse Input
    data = request.get_json()
    if not data or "phoneNumber" not in data:
        return jsonify({"error": "Missing phoneNumber field"}), 400

    phone_number = data["phoneNumber"]
    
    # 3. Lookup Logic
    customer = CUSTOMER_DB.get(phone_number)
    
    # 4. Construct Response
    if customer:
        response_data = {
            "status": "success",
            "data": customer
        }
        return jsonify(response_data), 200
    else:
        response_data = {
            "status": "not_found",
            "data": None
        }
        # Return 200 OK even for not found to prevent Architect error port trigger
        return jsonify(response_data), 200

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

Architect Flow JSON Snippet

If you are importing flows via JSON, here is the relevant section for the GetExternalContactAction node.

{
  "id": "external-contact-node-1",
  "type": "GetExternalContactAction",
  "name": "Customer Lookup",
  "description": "Retrieves customer profile based on caller ID",
  "configuration": {
    "method": "POST",
    "url": "https://your-ngrok-url.ngrok.io/api/customer/lookup",
    "headers": {
      "Authorization": "Bearer your_secure_secret_token_here",
      "Content-Type": "application/json"
    },
    "body": "{\n  \"phoneNumber\": \"{{contact.attributes.caller.phoneNumber}}\"\n}",
    "timeout": 3000,
    "responseMapping": {
      "status": "customerLookup.status",
      "data.customerId": "customerLookup.customerId",
      "data.firstName": "customerLookup.firstName",
      "data.lastName": "customerLookup.lastName",
      "data.tier": "customerLookup.tier"
    }
  },
  "edges": {
    "success": "decision-node-1",
    "error": "error-handler-node-1"
  }
}

Common Errors & Debugging

Error: 401 Unauthorized

  • Cause: The Authorization header in the Architect node does not match the SECRET_TOKEN in your Python backend.
  • Fix: Verify the token string is identical in both places. Ensure there are no trailing spaces. Check the Python logs for the received header value.

Error: 400 Bad Request

  • Cause: The JSON body sent by Genesys Cloud is malformed or missing the phoneNumber field.
  • Fix: Check the variable mapping in the Architect node. Ensure {{contact.attributes.caller.phoneNumber}} is valid. If the caller did not provide a phone number (e.g., some VoIP providers), this variable may be empty or null. Add a check in your Python code for empty strings.

Error: Timeout (3000ms exceeded)

  • Cause: Your backend service is too slow to respond.
  • Fix: Optimize your database query. Ensure the network latency between Genesys Cloud data centers and your server is low. Consider increasing the timeout in Architect if necessary, but aim for sub-1-second responses.

Error: Variable Mapping Failure

  • Cause: The JSON response from your backend does not match the expected structure in the Architect node.
  • Fix: Use a tool like Postman to send a test request to your backend and verify the JSON structure. Ensure the keys in the responseMapping section of Architect exactly match the JSON keys returned by your backend. For example, if your backend returns {"data": {"firstName": "Alice"}}, the mapping must be data.firstName, not firstName.

Error: Phone Number Format Mismatch

  • Cause: Genesys Cloud sends the phone number in E.164 format (e.g., +15551234567), but your database stores it differently (e.g., 15551234567).
  • Fix: Normalize the phone number in your Python backend before lookup. Strip the + sign or leading zeros if necessary.
# Example normalization in Python
def normalize_phone(phone):
    if phone.startswith("+"):
        return phone[1:]
    return phone

Official References