> ## Documentation Index
> Fetch the complete documentation index at: https://x-preview-mintlify-7f49d7a1.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Solución de problemas

> Diagnostica problemas de cifrado en X Chat: errores del Chat XDK, recuperación de copia de claves, fallos de descifrado y payloads firmados.

Esta página cubre problemas **específicos del cifrado de X Chat y del Chat XDK**—claves, copia de seguridad segura de claves, descifrar/verificar y construcción de payloads cifrados de envío.

Para webhooks, OAuth, códigos de estado HTTP y límites de tasa, usa la documentación general de la [X API](/x-api/introduction) y de [autenticación](/fundamentals/authentication/overview).

***

## Claves y copia de seguridad segura de claves

### El desbloqueo falla (código de acceso inválido)

* Confirma que el código de acceso coincide con el usado en `setup`
* Espera entre intentos; los realms limitan por tasa los intentos incorrectos y pueden bloquear la recuperación tras demasiados fallos

<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>

### El cifrado o descifrado falla porque las claves o la identidad no están configuradas

Carga primero las claves privadas y luego establece la **identidad de sesión**—tu ID de usuario más el `public_key_version` de tu registro en X. Los métodos `encrypt_*` y `prepare_*` firman con ella; llamarlos sin identidad de sesión (y sin una sobrecarga explícita por llamada) es un error.

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

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

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

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

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

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

### Tu clave pública local nunca coincide con las claves registradas de la cuenta

A menudo los clientes necesitan responder *"¿la clave en este dispositivo es una de las claves registradas para esta cuenta?"* — tras una restauración o importación, para adoptar el `public_key_version` correcto, o para decidir si el onboarding ya ocurrió. Comparar la salida de `get_public_keys` del Chat XDK contra el campo `public_key` de la API **como cadenas siempre falla**, incluso para la misma clave, porque las dos usan codificaciones distintas:

* La **API** almacena y devuelve la clave exactamente como la subió el registro: la codificación DER (SPKI) — la clave en bruto detrás de un prefijo fijo de identificador de algoritmo
* La `get_public_keys` del **Chat XDK** devuelve la clave en bruto sola, sin ese prefijo

La misma clave, dos escrituras. Para compararlas, decodifica ambas desde base64 y comprueba que los bytes de la API **terminan con** los bytes del SDK (bytes idénticos también coinciden, por si ambas partes alguna vez tienen la misma codificación):

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import base64

    def same_key(local_b64: str, server_b64: str) -> bool:
        local = base64.b64decode(local_b64)    # chat.get_public_keys()["identity"]
        server = base64.b64decode(server_b64)  # API row's "public_key"
        return local == server or (len(server) > len(local) and server.endswith(local))
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const sameKey = (localB64: string, serverB64: string): boolean => {
      const local = Buffer.from(localB64, 'base64');   // chat.getPublicKeys().identity
      const server = Buffer.from(serverB64, 'base64'); // API row's public_key
      return local.equals(server) ||
        (server.length > local.length && server.subarray(server.length - local.length).equals(local));
    };
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    fn same_key(local: &[u8], server: &[u8]) -> bool {
        local == server || (server.len() > local.len() && server.ends_with(local))
    }
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    func sameKey(local, server []byte) bool {
        return bytes.Equal(local, server) ||
            (len(server) > len(local) && bytes.HasSuffix(server, local))
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    static bool SameKey(byte[] local, byte[] server) =>
        local.SequenceEqual(server) ||
        (server.Length > local.Length &&
         server.AsSpan(server.Length - local.Length).SequenceEqual(local));
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    static boolean sameKey(byte[] local, byte[] server) {
        if (Arrays.equals(local, server)) return true;
        if (server.length <= local.length) return false;
        byte[] tail = Arrays.copyOfRange(server, server.length - local.length, server.length);
        return Arrays.equals(tail, local);
    }
    ```
  </Tab>
</Tabs>

Una vez que coincidan, adopta el `public_key_version` de esa fila para `set_identity`. Cuando compares versiones (por ejemplo, para elegir la clave más reciente), compara **numéricamente** — las versiones son marcas de tiempo en milisegundos de longitud de cadena variable, por lo que una comparación lexicográfica elige la incorrecta.

### Falta la clave de conversación para un mensaje

Un error como `Message encrypted with key version '…' but no matching key found` significa que no tienes la clave **en bruto** para el `conversation_key_version` de ese mensaje.

1. Descifra el material de clave desde `conversation_key_change_event` (eventos en vivo) o `meta.conversation_key_events` (historial) con `extract_conversation_keys`, **o** incluye esos blobs en `decrypt_events`—con `set_cache_keys(true)` habilitado, `decrypt_events` también retiene la clave verificada más reciente de cada conversación para que las llamadas posteriores a `decrypt_event` y `encrypt_*` puedan omitirla
2. Confirma que se agregaron claves de conversación para esa versión y que sigues siendo participante (consulta [Primeros pasos](/xchat/getting-started#4-set-up-conversation-keys))

### El par no tiene claves públicas

Quizás no ha completado la incorporación. Después de que se registren, carga `public_key`, `signing_public_key`, `identity_public_key_signature` y `public_key_version` desde **API reference → Encryption keys**.

***

## Descifrado y firmas

### El descifrado falla

* Clave de conversación **en bruto** desactualizada o incorrecta, o versión de clave incorrecta
* Cadena `encoded_event` incompleta
* El tipo de evento no es un mensaje cifrado que puedas tratar como contenido descifrable

### La firma no verifica

La verificación es **fail-closed por defecto** (`reject_unverified = true`): el SDK ya rechaza los eventos firmados no verificados, así que un fallo aquí significa que las entradas de verificación son incorrectas, no que debas activar la comprobación. Causas comunes:

* Entrada de clave de firma faltante o incompleta para el **remitente** (todos los campos que requiere el Chat XDK—consulta la referencia del [Chat XDK](/xchat/xchat-xdk))
* No se pasaron claves de firma en la llamada y ninguna está almacenada mediante `set_signing_keys`
* El remitente rotó versiones—vuelve a obtener sus claves públicas
* Una versión de clave por debajo del piso aceptado nunca verifica
* En un **evento de cambio de clave de grupo**, quien lo firmó ya abandonó el grupo, así que sus claves ya no se sirven — consulta [Cambios de clave de miembros que se fueron](/xchat/groups#cambios-de-clave-de-miembros-que-se-fueron)

El setter `set_reject_unverified` existe para **optar por salir** de este predeterminado (`false`, no recomendado). Si lo desactivaste antes, restaura el predeterminado fail-closed:

<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>

### Una respuesta lleva `reply_preview_validation: "Invalid"`

Las respuestas descifradas pueden llevar `reply_preview_validation` (`"Valid"` / `"Invalid"`; JavaScript usa `'valid'` / `'invalid'`). `Invalid` significa que la vista previa citada dentro del mensaje no coincide con el evento original firmado que incrusta—trata la cita como no confiable y renderiza el contenido citado solo desde el original validado. El mensaje en sí se verifica por separado y sigue siendo auténtico; no se lanza ninguna excepción por una vista previa inválida.

### Los eventos antiguos fallan la verificación de forma permanente

Errores como `signature missing or no matching signing key` o un desajuste ECDSA en eventos **antiguos** son permanentes. Las firmas son inmutables y se verifican reconstruyendo el payload firmado desde el evento en sí, así que un evento firmado sobre bytes distintos (o nunca firmado) fallará en cada carga futura—ninguna reintentación, actualización de claves o llamada a la API puede sanarlo. Trata estos eventos como tombstones, no como errores reintentables. Rotar la clave de conversación inicia un historial limpio y verificable desde ese punto en adelante; los mensajes nuevos no se ven afectados.

***

## Construcción del payload de envío

Estos errores son específicos del cifrado de X Chat (no errores HTTP generales):

| Problema                          | Solución                                                                                                                                                                                                                                                    |
| :-------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bytes de clave incorrectos        | Pasa los bytes de la clave de conversación **en bruto** al Chat XDK, no la cadena de clave cifrada que retorna la API                                                                                                                                       |
| Nombres de campo JSON incorrectos | Mapea `encrypted_content` → `encoded_message_create_event` y `encoded_event_signature` → `encoded_message_event_signature`                                                                                                                                  |
| Id de mensaje incorrecto          | Envía el `message_id` del payload devuelto—el SDK lo genera y lo incrusta en el evento firmado, así que cualquier otro valor falla. En los reintentos, reutiliza el mismo payload cifrado para que el id nunca se acuñe dos veces                           |
| Desajuste de versión              | Alinea `conversation_key_version` con la clave que usas; alinea la versión de la clave de firma pasada a `set_identity` con tu registro de clave pública                                                                                                    |
| Forma del id en la ruta           | Las rutas URL siguen necesitando el id de conversación con guión (`:` → `-`), pero para firmar el SDK acepta cualquier forma: `A:B`, `A-B` (en cualquier orden) o solo el id de usuario del destinatario—todos se canonicalizan a los mismos bytes firmados |

### La API devuelve 400 en una llamada que cambia estado

Cada llamada de chat que cambia estado—agregar o rotar claves de conversación, crear un grupo, agregar miembros—requiere **`action_signatures`** en el cuerpo de la solicitud, validado en el límite de la API. Una entrada faltante o mal formada (cada una necesita `message_id`, `encoded_message_event_detail` y un `message_event_signature` con `signature`, `public_key_version` y `signature_version`) devuelve inmediatamente una respuesta HTTP 400 problem-details. Usa los métodos prepare del SDK (`prepare_conversation_key_change`, `prepare_group_create`, `prepare_group_members_change`) y envía **todas** las firmas devueltas—crear un grupo y añadir miembros devuelven dos.

***

## Cifrar y descifrar multimedia

* Usa la **misma** clave de conversación (y versión) que el mensaje que referencia el adjunto
* Trata las respuestas de descarga como **texto cifrado** hasta que ejecutes `decrypt_stream`
* Infiere el tipo MIME **después** de descifrar; el `Content-Type` de la descarga a menudo no es el tipo real de imagen

Detalles: [Multimedia](/xchat/media).

***

## Depuración segura

Al investigar fallos criptográficos:

* Registra solo los ids de conversación, ids de evento y **versiones** de clave
* **No** registres texto plano, códigos de acceso, claves privadas ni blobs de clave completos
* Confirma que la versión de clave de firma pasada a `set_identity` coincide con el `public_key_version` de tu registro de clave pública
* Para historial incompleto, pagina **todas** las páginas de eventos para no saltarte metadatos de cambio de clave antes de descifrar
