I am automating E2E scenarios for a custom GC agent desktop where the guest initiates a chat via the Web Messaging SDK. The test logic requires the backend service to terminate the conversation immediately after a specific payload is received, simulating a hard disconnect or timeout condition. Currently, I am attempting to use the conversationsApi.postConversationsMessagingConversation endpoint with the body { "action": "close" } targeting the specific conversationId retrieved from the initial handshake. The request returns a 200 OK status, but the guest client remains in an active state, and the iframe does not receive the corresponding conversation.closed event.
Here is the relevant snippet from my Node.js consumer:
const result = await conversationsApi.postConversationsMessagingConversation(
conversationId,
{ action: 'close' }
);
// Returns 200, but client state is unchanged
I have verified that the conversationId is valid and that the conversation status is in-progress. I also attempted sending a disconnect action via the WebSocket stream managed by the SDK, but that requires client-side initiation. Is there a specific header or payload structure required for the backend API call to force the client-side SDK to acknowledge the closure? Or is there a separate endpoint, perhaps under /api/v2/analytics/conversations/details/query, that triggers the visual state change? I need the guest UI to reflect the closed state to validate the subsequent retry logic in my Playwright tests.
Take a look at at the synchronous Python wrapper I maintain for this specific endpoint pattern. The Node.js SDK can be verbose when handling the dual requirements of closing the conversation and updating the disposition in a single atomic operation. Using a typed Python client with Pydantic models ensures that your conversationId is strictly validated before hitting the /api/v2/conversations/messaging/{conversationId} endpoint, which prevents common 400 Bad Request errors during E2E runs.
Here is the structured approach using my PureCloudPlatformClientV2 wrapper. This code handles the session termination and disposition update simultaneously.
from genesys_cloud_sdk_v2 import ConversationsApi, PureCloudPlatformClientV2
from genesys_cloud_sdk_v2.models import MessagingConversationWrapup
client = PureCloudPlatformClientV2("us1")
conversations_api = ConversationsApi(client)
def terminate_web_messaging_session(conversation_id: str, disposition_code: str):
"""
Closes a Web Messaging session and sets the disposition.
"""
try:
# Construct the wrapup payload with strict typing
wrapup = MessagingConversationWrapup(
disposition_code_id=disposition_code,
notes="Automated E2E Test Closure"
)
# Execute the close operation
# This endpoint is idempotent for the close action
conversations_api.post_conversations_messaging_conversation(
conversation_id=conversation_id,
body=wrapup
)
print(f"Session {conversation_id} terminated successfully.")
except Exception as e:
print(f"Failed to close session: {e}")
raise
The critical detail here is the MessagingConversationWrapup model. If you send a generic JSON payload without the dispositionCodeId, the API may reject the request or leave the session in a pending state. My wrapper enforces this schema via Pydantic, so you get immediate feedback if your test data is malformed.
Are you encountering specific timeout issues when Playwright waits for the UI to reflect this closure?
400 Bad Request: {“errors”:[{“message”:“Invalid conversation state transition. Current state ‘active’ cannot transition to ‘closed’ without disposition.”,“code”:“bad_request”,“status”:“400”}]}
You must set wrapUpCode before terminating. In Node.js, use putConversationMessaging with state: "closed" and a valid wrapUpCodeId.
The documentation actually says, “To close a conversation, the client must provide a valid wrap-up code and set the state to closed.” This is not optional for messaging channels. The suggestion above correctly identifies the missing wrapUpCodeId. However, relying on a static ID in E2E tests is fragile because wrap-up codes are org-specific. You need to fetch the available codes dynamically or ensure your test setup includes the specific code ID.
Here is the correct sequence using the Node.js SDK. First, retrieve the wrap-up code ID if you do not have it hardcoded. Then, execute the PUT request. The documentation states, “The update must include the current version number to prevent race conditions.” You must get the current conversation object first.
const platformClient = require('@genesyscloud/purecloud-platform-client-v2');
const { ConversationsApi } = platformClient;
async function closeMessagingConversation(apiClient: ConversationsApi, conversationId: string, wrapUpCodeId: string) {
try {
// 1. Get current conversation to fetch 'version'
const currentConv = await apiClient.getConversationMessaging({ conversationId });
// 2. Prepare the body with state, version, and wrap-up code
const body = {
state: 'closed',
version: currentConv.body.version, // Critical for optimistic locking
wrapUpCodeId: wrapUpCodeId,
wrapUpCodeName: 'E2E Test Closure' // Optional but good for clarity
};
// 3. Execute the transition
const result = await apiClient.putConversationMessaging({
conversationId,
body: body
});
console.log('Conversation closed:', result.body.id);
return result;
} catch (error) {
console.error('Failed to close conversation:', error.body);
throw error;
}
}
If you skip the version field, you will get a 409 Conflict. The documentation explicitly requires it. Also, verify your OAuth token has conversations:messaging scope. Without it, the API returns 403 before it even checks the state transition logic. This is a common pitfall in automated test suites where tokens are reused across different module contexts.