> ## Documentation Index
> Fetch the complete documentation index at: https://docs.x.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Diagnose common X Chat encryption issues including Chat XDK errors, secure key backup recovery, decryption failures, and building signed send payloads.

This page covers problems that are **specific to X Chat encryption and the Chat XDK**—keys, secure key backup, decrypt/verify, and building encrypted send payloads.

For webhooks, OAuth, HTTP status codes, and rate limits, use the general [X API](/x-api/introduction) and [authentication](/fundamentals/authentication/overview) documentation.

***

## Keys and secure key backup

### Unlock fails (invalid passcode)

* Confirm the passcode matches the one used with `setup`
* Wait between attempts; realms rate-limit wrong guesses and can lock recovery after too many failures

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        chat.unlock(passcode)
    except ValueError as e:
        print(e)  # may mention InvalidPin or guesses remaining
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    try {
      await chat.unlock(passcode);
    } catch (e) {
      console.error((e as Error).message);
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.unlock(passcode_bytes).await?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    if err := chat.Unlock(passcode, juiceboxConfigJSON); err != nil {
        log.Println(err)
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    try { chat.Unlock(passcode, juiceboxConfigJson); }
    catch (Exception e) { Console.WriteLine(e.Message); }
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    try { chat.unlock(passcode, juiceboxConfigJson); }
    catch (Exception e) { System.out.println(e.getMessage()); }
    ```
  </Tab>
</Tabs>

### Encrypt or decrypt fails because keys are not loaded

Load private keys first, then set the public-key **version** from your record on X.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    chat.unlock(passcode)  # or: chat.import_keys(blob)
    chat.set_key_version(signing_key_version)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await chat.unlock(passcode);
    chat.setKeyVersion(signingKeyVersion);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.import_keys(&blob)?;
    chat.set_key_version(&signing_key_version);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    blob, _ := chatxdk.Base64ToBytes(privateKeysB64)
    _ = chat.ImportKeys(blob)
    chat.SetKeyVersion(signingKeyVersion)
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    chat.ImportKeys(blobBytes);
    chat.SetKeyVersion(signingKeyVersion);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    chat.importKeys(blobBytes);
    chat.setKeyVersion(signingKeyVersion);
    ```
  </Tab>
</Tabs>

### Missing conversation key for a message

You do not have the **raw** key for that message’s `conversation_key_version`.

1. Decrypt key material from `conversation_key_change_event` (live events) or `meta.conversation_key_events` (history) with `extract_conversation_keys`, **or** include those blobs in `decrypt_events`
2. Confirm conversation keys were added for that version and you are still a participant (see [Getting Started](/xchat/getting-started#4-set-up-conversation-keys))

### Peer has no public keys

They may not have finished onboarding. After they register, load `public_key`, `signing_public_key`, `identity_public_key_signature`, and `public_key_version` from **API reference → Encryption keys**.

***

## Decryption and signatures

### Decrypt fails

* Stale or wrong **raw** conversation key, or wrong key version
* Incomplete `encoded_event` string
* Event type is not an encrypted message you can treat as decryptable content

### Signature does not verify

Verification is **fail-closed by default** (`reject_unverified = true`): the SDK already rejects unverified signed events, so a failure here means the verification inputs are wrong, not that you need to turn checking on. Common causes:

* Missing or incomplete signing-key entry for the **sender** (all fields required by the Chat XDK—see the [Chat XDK](/xchat/xchat-xdk) reference)
* The sender rotated versions—re-fetch their public keys
* A key version below the accepted floor never verifies

The `set_reject_unverified` setter exists to opt **out** of this default (`false`, not recommended). If you disabled it earlier, restore the fail-closed default:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    chat.set_reject_unverified(True)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    chat.setRejectUnverified(true);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    chat.set_reject_unverified(true);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    chat.SetRejectUnverified(true)
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    chat.SetRejectUnverified(true);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    chat.setRejectUnverified(true);
    ```
  </Tab>
</Tabs>

### Old events permanently fail verification

Errors like `signature missing or no matching signing key` or an ECDSA mismatch on **old** events are permanent. Signatures are immutable and verified by rebuilding the signed payload from the event itself, so an event that was signed over different bytes (or never signed) fails on every future load—no retry, key refresh, or API call can heal it. Treat these events as tombstones, not retryable errors. Rotating the conversation key starts a clean, verifiable history from that point forward; new messages are unaffected.

***

## Building the send payload

These mistakes are specific to X Chat encryption (not general HTTP errors):

| Issue                  | Fix                                                                                                                                                                                                             |
| :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Wrong key bytes        | Pass the **raw** conversation key bytes into the Chat XDK, not the encrypted key string from the API                                                                                                            |
| Wrong JSON field names | Map `encrypted_content` → `encoded_message_create_event` and `encoded_event_signature` → `encoded_message_event_signature`                                                                                      |
| Missing message id     | Generate `message_id` yourself and send the same value in the request body                                                                                                                                      |
| Version mismatch       | Align `conversation_key_version` with the key you use; align signing key version with `set_key_version` / your public-key record                                                                                |
| Path id form           | URL paths still need the hyphenated conversation id (`:` → `-`), but for signing the SDK accepts any form: `A:B`, `A-B` (either order), or the bare recipient user id—all canonicalize to the same signed bytes |

### API returns 400 for a state-changing call

Every state-changing chat call—adding or rotating conversation keys, creating a group, adding members—requires **`action_signatures`** in the request body, validated at the API boundary. A missing or malformed entry (each needs `message_id`, `encoded_message_event_detail`, and a `message_event_signature` with `signature`, `public_key_version`, and `signature_version`) returns an HTTP 400 problem-details response immediately. Use the SDK prepare methods (`prepare_conversation_key_change`, `prepare_group_create`, `prepare_group_members_change`) and send **all** returned signatures—group create and member adds return two.

***

## Media encrypt and decrypt

* Use the **same** conversation key (and version) as the message that references the attachment
* Treat download responses as **ciphertext** until you run `decrypt_stream`
* Infer MIME type **after** decrypt; the download `Content-Type` is often not the real image type

Details: [Media](/xchat/media).

***

## Safe debugging

When investigating crypto failures:

* Log conversation ids, event ids, and key **versions** only
* Do **not** log plaintext, passcodes, private keys, or full key blobs
* Confirm `set_key_version` matches the `public_key_version` on your public-key record
* For incomplete history, page **all** event pages so key-change metadata is not skipped before decrypting
