Setting Wrap-up Codes via GraphQL Gateway on Conversation Termination

Just noticed that the standard conversation termination flow in my Node.js gateway does not allow me to set a wrap-up code programmatically when I use POST /api/v2/conversations/{conversationId}/disconnect. The endpoint performs a full conversation teardown and applies a system wrap-up code to any participants that are pending wrap-up, but it does not accept a payload for a custom disposition. I need to inject a specific code (e.g., “QA_Review”) into the interaction record before the wrap-up timer starts or immediately upon closure.

My GraphQL schema exposes a terminateConversation(id: ID!, wrapUpCode: String) mutation. I want this to be atomic. Currently, I am fetching the conversation, disconnecting it, and then attempting to update the interaction, but the timing is racy. The interaction state often transitions to “closed” before my subsequent update request lands, resulting in a 409 Conflict or the update being silently ignored by the analytics engine.

Here is my current resolver logic using the Platform SDK:

const { ConversationApi } = require('@genesys/cloud/convapi');
const convApi = new ConversationApi();

async function terminateWithWrapUp(convId, code) {
 // 1. End the conversation
 await convApi.disconnectConversation(convId);
 
 // 2. Attempt to set wrap-up
 // This fails because the conversation is already gone
 const interactionId = await getInteractionIdFromCache(convId);
 await convApi.putInteractionDisposition(interactionId, {
 code: code
 });
}

I have reviewed the API docs for POST /api/v2/conversations/{conversationId}/disconnect. It performs a full conversation teardown and applies a system wrap-up code to any participants that are pending wrap-up, but does not seem to support a wrapUpCode field in the request for the termination itself.

  • I tried chaining an update to the interaction endpoint immediately after the disconnect. The interaction ID is retrieved from a local Redis cache. The race condition causes the wrap-up to fail silently or return a 409.
  • I attempted to use the GET /api/v2/conversations/{conversationId}/participants/{participantId}/wrapup endpoint to verify state, but this is a read-only operation and does not allow me to set the code. Furthermore, my backend service does not have the specific participant context required to target the correct wrap-up record reliably in this race condition.

Is there a supported API pattern to set the disposition code at the exact moment of programmatic termination for a machine-initiated conversation? Or should I be using a different endpoint entirely to bridge this gap in the GraphQL gateway?

3 Likes

the delete endpoint is strictly for tearing down the connection, so it ignores any disposition payload. you need to apply the wrap-up code to the participant’s communication first before killing the session.

await fetch(`/api/v2/conversations/messages/${id}/participants/${participantId}/communications/${communicationId}/wrapup`, {
 method: 'POST',
 body: JSON.stringify({ wrapUpCode: 'QA_Review' })
});
await fetch(`/api/v2/conversations/${id}/disconnect`, { method: 'POST' });

are you trying to set the code before the agent logs off or just tagging the record after? the DELETE endpoint won’t take a payload, that’s expected. you need to use the update method instead.

const { PureCloudPlatformClientV2 } = require('@genesyscloud/platform-client');
const platformClient = PureCloudPlatformClientV2.init();

async function setWrapUp(conversationId, codeName) {
 try {
 // 1. Fetch the code ID first
 const codes = await platformClient.RoutingApi.getRoutingQueuesQueueIdWrapupcodes({
 queueId: 'your-queue-id', 
 pageSize: 200
 });
 const codeId = codes.entities.find(c => c.name === codeName)?.id;

 if (!codeId) throw new Error('Code not found');

 // 2. Note: The REST API does not expose a direct endpoint to set a wrap-up code on a conversation participant.
 // Wrap-up codes are typically applied via the WebSocket API or by the agent in the UI.
 // You can verify the current wrap-up state using the GET endpoint:
 // await platformClient.ConversationsApi.getConversationsConversationIdParticipantsParticipantIdWrapup({
 //   conversationId,
 //   participantId: 'your-participant-id'
 // });
 
 console.log('Wrap-up code identified. Apply via WebSocket or Agent UI.');
 } catch (err) {
 console.error('Failed to process wrap-up:', err);
 }
}

make sure your OAuth token has conversation:read scope. this hits /api/v2/conversations/{conversationId}/participants/{participantId}/wrapup for verification. don’t try to patch the conversation object directly, it ignores the disposition field.

1 Like

patching the participant is the right move, but you’ll run into race conditions if the delete hits before the patch processes. the api is async, so you need a small delay or a polling check. also, make sure you’re using the correct participantId. if you’re using the gateway, that’s usually the bot or the agent, not the guest.

try this pattern. it waits for the patch to confirm before tearing down.

async function endWithWrapUp(convId, partId, commId, code) {
 // 1. set wrap-up
 const patchRes = await fetch(`/api/v2/conversations/messages/${convId}/participants/${partId}/communications/${commId}/wrapup`, {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({ wrapUpCode: code })
 });

 if (!patchRes.ok) {
 console.error('failed to set wrap-up:', await patchRes.text());
 return;
 }

 // 2. small buffer to ensure backend processes it
 await new Promise(r => setTimeout(r, 200));

 // 3. close session
 await fetch(`/api/v2/conversations/${convId}/disconnect`, { method: 'POST' });
}

if you’re doing this for QA_Review, you might also want to tag the conversation. wrap-up codes are for reporting, tags are for filtering. setting both gives you better analytics later. the admin UI makes it easy to see wrap-up distribution, but tags help you pull specific interactions for review without digging through all ‘QA_Review’ codes if you have multiple reasons.

also, check your queue settings. if the wrap-up code is mandatory, the agent might get stuck in wrap-up state if the system doesn’t register the code correctly. i’ve seen that happen when the participantId was wrong. double check the participant object returned by the GET endpoint. it’s easy to mix up the guest and agent IDs.

one more thing. if you’re using GraphQL, the mutation might not support the delete directly. stick to REST for the teardown. GraphQL is better for reading state or updating small fields. mixing them in a single flow can get messy with auth tokens and caching. keep it simple. rest for delete, rest for patch. done.

patching the participant is the right path, but doing it sequentially in a gateway adds latency. i usually handle this in go by firing the wrap-up update and the conversation termination concurrently, since the termination is idempotent enough for our use case.

here’s the pattern i use with platformclientv2. you don’t need to wait for the patch to fully resolve before deleting, just ensure the http request is sent.

// update wrap-up code
wrapUpdate := platformclientv2.Conversationparticipant{
 WrapUpCode: &platformclientv2.Wrappupcode{ Id: &codeID },
}
_, err := client.ConversationsApi.ConversationsParticipantPatch(conversationId, participantId, wrapUpdate)

// delete conversation immediately after
if err == nil {
 client.ConversationsApi.ConversationsWebmessagingConversationDelete(conversationId)
}

it’s risky if the patch fails silently, so i wrap it in a retry middleware. if the patch fails, the conversation still ends but without the code. trade-off for speed. make sure your oauth token has conversation:write scope.

1 Like