Issue setting wrap-up codes via PATCH on ended interactions

Looking for some advice on troubleshooting this 400 Bad Request when attempting to assign wrap-up codes programmatically after an interaction concludes.

I am building a Django service with Celery workers that listens for interaction end events. Once the wrapup status is reached, I trigger a task to PATCH the conversation via /api/v2/conversations/{conversationId}. My goal is to populate wrapup.code and wrapup.notes before the interaction is fully archived.

Here is the payload I am sending:

{
 "wrapup": {
 "code": {
 "id": "12345-67890-abcde",
 "name": "Account Updated"
 },
 "notes": "Updated billing info per request."
 }
}

Error: 400 Bad Request - “The conversation is not in a valid state to be updated.”

The interaction is definitely in the wrapup state when the PATCH hits. I have conversation:write scope. Is there a race condition between the state transition and the API lock? Or is the endpoint strictly reserved for manual agent input?

I’d suggest checking out at the wrapup object structure in the request body, because sending a full conversation payload instead of just the updated wrapup details often triggers a 400 due to missing required fields like id or version.

  • PATCH /api/v2/conversations/{conversationId}
  • wrapup.code
  • wrapup.notes
  • ETag header requirements

The root cause here is the strict versioning constraints on the conversation resource. The suggestion above regarding the payload structure is correct, but you must also handle the ETag header. Genesys Cloud uses optimistic locking to prevent race conditions. If your Celery worker sends a PATCH without the current version number in the If-Match header, or if the version has changed since the event fired, the API returns a 400 or 412.

HTTP/1.1 412 Precondition Failed
{“code”:412,“message”:“The precondition given in one or more of the request-header fields evaluated to false when tested on the server.”}

You need to fetch the latest version before applying the wrap-up code. In Java, this looks like:

ConversationApi conversationApi = platformClient.conversationsApi();
ConversationResponse current = conversationApi.getConversation(conversationId, null, null, null);
ConversationRequest update = new ConversationRequest();
update.setWrapupCode(new WrapupCodeRequest().id("your-code-id").notes("Auto-applied"));
conversationApi.patchConversation(conversationId, update, null, null, current.getVersion(), null);

Ensure your Django service retrieves the version immediately after the event to minimize drift.

It depends, but generally… the 412 error confirms the version mismatch issue. You are likely sending the version from the event payload, which is stale by the time your Celery worker executes. The API rejects any PATCH where the If-Match header does not exactly match the current server-side version. This is standard optimistic locking behavior in Genesys Cloud to prevent data corruption during concurrent updates.

To fix this, you must fetch the current conversation state immediately before attempting the update. Do not rely on cached data or event payloads for versioning. Use the PureCloud SDK to get the latest object, then apply your changes and send the patched version. Here is the correct flow using the Python SDK:

  1. Fetch the current conversation to get the latest version.
  2. Update the wrapup fields in the local object.
  3. Send the PATCH request with the updated object and the correct If-Match header.
from purecloudplatformclientv2 import ConversationApi, WrapupCode

api = ConversationApi()
try:
 # 1. Fetch current state to get valid version
 conv = api.get_conversation(conversation_id=conv_id)
 
 # 2. Update wrapup details
 conv.wrapup = WrapupCode(
 code_id="your-wrapup-code-id",
 notes="Automated notes from Celery",
 duration_ms=0,
 user_id="system-user-id" # Optional: who is setting this
 )
 
 # 3. Patch with ETag (version) for optimistic locking
 api.patch_conversation(
 conversation_id=conv_id,
 body=conv,
 if_match=str(conv.version)
 )
except Exception as e:
 print(f"Failed to update wrapup: {e}")

Always handle the 412 or 409 errors by retrying the fetch-and-patch sequence once. If it fails twice, the conversation is likely already closed or modified by another process. Do not ignore these errors, as they indicate data integrity risks. This pattern ensures your Django service stays in sync with the platform’s state machine.

Check your CI validation steps for the conversation versioning logic. The previous answers correctly identified the If-Match header requirement, but they missed the operational risk of stale versions in asynchronous workers.

Cause:
Your Celery worker processes events with latency. By the time the PATCH request reaches Genesys Cloud, the conversation version has likely incremented due to other system updates or wrap-up actions. Sending the version from the initial event payload guarantees a 412 Precondition Failed or 400 Bad Request because the server rejects outdated state.

Solution:
Implement a “refresh-and-patch” pattern in your worker. Do not trust the event payload’s version. Fetch the current resource first, then apply your updates. Here is the Python structure using requests that I use in my integration tests:

import requests

def update_wrapup(conversation_id, wrapup_code, notes):
 # 1. Fetch current state to get latest version
 get_resp = requests.get(
 f"https://api.mypurecloud.com/api/v2/conversations/{conversation_id}",
 headers={"Authorization": f"Bearer {token}"}
 )
 if get_resp.status_code != 200:
 raise Exception("Failed to fetch conversation")
 
 current_data = get_resp.json()
 current_version = current_data.get('version')
 
 # 2. Prepare PATCH payload with only necessary fields
 patch_payload = {
 "wrapup": {
 "code": {"id": wrapup_code},
 "notes": notes
 }
 }
 
 # 3. Execute PATCH with strict versioning
 patch_resp = requests.patch(
 f"https://api.mypurecloud.com/api/v2/conversations/{conversation_id}",
 json=patch_payload,
 headers={
 "Authorization": f"Bearer {token}",
 "If-Match": f'"{current_version}"',
 "Content-Type": "application/json"
 }
 )
 return patch_resp.status_code

This ensures the If-Match header always reflects the live state. I run this pattern in my GitHub Actions CI pipeline to verify API contract stability before merging Terraform changes. It eliminates the race condition entirely.