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

# Eventos de X Chat en tiempo real

> Recibe eventos chat.received, chat.sent y otras actividades cifradas de X Chat mediante webhooks o activity stream y descifra con el Chat XDK.

X entrega **`chat.received`**, **`chat.sent`** y actividad relacionada de X Chat con **texto cifrado** en el payload. Descifra con el [Chat XDK](/xchat/xchat-xdk).

| Capa               | Función                                                                                                                                  |
| :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| **X Activity API** | `GET /2/activity/stream`; `POST` / `GET` / `PUT` / `DELETE` `/2/activity/subscriptions` (consulta la seguridad de OpenAPI por operación) |
| **Webhooks**       | Rutas opcionales `POST` / `GET` `/2/webhooks` y `PUT` / `DELETE` `/2/webhooks/{webhook_id}` si terminas en tu propia URL HTTPS           |
| **Chat XDK**       | `decrypt_event` / `decrypt_events`, con los almacenes de sesión `set_signing_keys` / `set_cache_keys`                                    |

Los tipos de evento privados de X Chat requieren autorización del usuario que monitoreas. Los adjuntos de archivos cifrados de X Chat usan **`media_hash_key`** y la descarga de multimedia de X Chat—no `expansions=attachments.media_keys` / `media.fields=variants` de la Post API.

***

## Tipos de evento

| Evento                   | Cuándo                                                      |
| :----------------------- | :---------------------------------------------------------- |
| `chat.received`          | El usuario suscrito recibe un DM cifrado                    |
| `chat.sent`              | El usuario suscrito envía un DM cifrado                     |
| `chat.conversation_join` | El usuario suscrito se une a un grupo (cuando se le ofrece) |

***

## 1. Elige el modo de entrega

**Activity stream (a menudo lo más simple para bots):** `GET /2/activity/stream` con un Bearer token de app (opcional `backfill_minutes`, `start_time`, `end_time` según OpenAPI). Filtra en el cliente por `chat.received` / `chat.sent`.

**Suscripciones de Activity:** gestiona suscripciones duraderas con:

* `POST /2/activity/subscriptions` — crear
* `GET /2/activity/subscriptions` — listar (paginado)
* `PUT /2/activity/subscriptions/{subscription_id}` — actualizar
* `DELETE /2/activity/subscriptions/{subscription_id}` o `DELETE /2/activity/subscriptions?ids=` — eliminar

Los cuerpos de solicitud y los scopes requeridos se definen en la operación OpenAPI de cada ruta. Crear una suscripción de la X Activity API (XAA) requiere **autorización de contexto de usuario** (OAuth 2.0 de contexto de usuario con los scopes correspondientes, como `dm.read` para eventos de chat) para el usuario cuya actividad monitoreas.

**Webhooks:** si terminas los eventos en tu endpoint HTTPS, registra un webhook con `POST /2/webhooks`, pasa los challenges CRC y luego crea tus suscripciones de actividad con `POST /2/activity/subscriptions`, haciendo referencia a tu `webhook_id` (consulta las operaciones Webhooks y Activity en OpenAPI). El XDK de Python/TypeScript puede exponer helpers para webhooks y actividad cuando tu versión del SDK los incluya.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from xdk import Client

    # Stream (app token) — exact helper names depend on your XDK version
    stream_client = Client(bearer_token="YOUR_BEARER_TOKEN")
    # for event in stream_client.activity.stream():
    #     handle_payload(event)  # see "Decrypt with the Chat XDK" below

    # Or create a subscription — requires user-context auth for the monitored user
    client = Client(access_token="YOUR_OAUTH2_USER_TOKEN")
    client.activity.create_subscription({
        "event_type": "chat.received",
        "filter": {"user_id": "USER_ID_TO_MONITOR"},
    })
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Client } from '@xdevplatform/xdk';

    // Creating a subscription requires user-context auth for the monitored user
    const client = new Client({ accessToken: 'YOUR_OAUTH2_USER_TOKEN' });
    await client.activity.createSubscription({
      event_type: 'chat.received',
      filter: { user_id: 'USER_ID_TO_MONITOR' },
    });
    // Stream: client.activity.stream() when available in your SDK version
    ```
  </Tab>
</Tabs>

Suscríbete también a `chat.sent` si necesitas copias salientes. Otros lenguajes: llama directamente a las mismas rutas HTTPS `/2/activity/*` (token de contexto de usuario para crear suscripciones, Bearer token de app para el stream).

***

## 2. CRC (solo webhooks)

Si usas webhooks, responde a los Challenge-Response Checks (GET `crc_token`) con HMAC-SHA256 del token usando el consumer secret, en la forma JSON que tu producto de webhook espera (típicamente `sha256=<base64>`).

***

## 3. Descifra con el Chat XDK

Campos en vivo: **`payload.encoded_event`**, opcional **`payload.conversation_key_change_event`**. Deduplica las entregas por **`event_uuid`**; deduplica los mensajes por el **`message_id`** que lleva el evento descifrado—forma parte del contenido firmado, mientras que los sequence ids son metadatos sin firmar asignados por el backend.

Los snippets de abajo usan los dos almacenes de sesión **opcionales** para el handler más corto: `set_signing_keys` mantiene las claves públicas de los participantes (obtenidas una vez del [endpoint de claves públicas](/x-api/chat/get-user-public-keys)), y `set_cache_keys(true)` conserva la clave verificada de cada conversación, así `decrypt_event` solo necesita el evento. Cuando un payload lleva `conversation_key_change_event`, pásalo antes por `decrypt_events`: eso verifica el cambio de clave y, con la caché activa, retiene su clave para la llamada a `decrypt_event`. ¿Prefieres no tener estado en la instancia? Pasa las claves por llamada en su lugar—consulta la nota al final de esta sección.

JavaScript usa tipos de evento en camelCase (`message`); los demás bindings usan `"Message"` y campos snake\_case.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # chat has keys loaded and set_identity called (see Getting Started)
    chat.set_cache_keys(True)
    chat.set_signing_keys(participant_signing_keys)  # all participants, from the public-key routes

    data = body.get("data") or {}
    if data.get("event_type") in ("chat.received", "chat.sent"):
        p = data.get("payload") or {}
        if p.get("conversation_key_change_event"):
            # Verify the key change and retain its key in the cache
            chat.decrypt_events([p["conversation_key_change_event"]])
        ev = chat.decrypt_event(p["encoded_event"])
        if ev["type"] == "Message":
            print(ev["sender_id"], ev["content"]["text"])
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    // chat has keys loaded and setIdentity called (see Getting Started)
    chat.setCacheKeys(true);
    chat.setSigningKeys(participantSigningKeys); // all participants, from the public-key routes

    const data = body?.data ?? {};
    if (data.event_type === 'chat.received' || data.event_type === 'chat.sent') {
      const p = data.payload ?? {};
      if (p.conversation_key_change_event) {
        // Verify the key change and retain its key in the cache
        chat.decryptEvents([p.conversation_key_change_event]);
      }
      const ev = chat.decryptEvent(p.encoded_event);
      if (ev.type === 'message') {
        console.log(ev.senderId, ev.content.text);
      }
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    // chat has keys loaded and set_identity called (see Getting Started)
    chat.set_cache_keys(true);
    chat.set_signing_keys(participant_signing_keys); // all participants

    if let Some(kc) = key_change.as_deref() {
        // Verify the key change and retain its key in the cache
        let _ = chat.decrypt_events(&[kc], &[]);
    }
    // Decrypt with the cached conversation key; verify against the stored signing keys
    let event = chat.decrypt_event(&encoded_event, &Default::default(), &[])?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // chat has keys loaded and SetIdentity called (see Getting Started)
    chat.SetCacheKeys(true)
    _ = chat.SetSigningKeys(participantSigningKeys) // all participants

    if keyChange != "" {
        // Verify the key change and retain its key in the cache
        _, _ = chat.DecryptEvents([]string{keyChange}, nil)
    }
    // Decrypt with the cached conversation key; verify against the stored signing keys
    event, err := chat.DecryptEvent(encodedEvent, nil, nil)
    if err == nil && event.Type == "Message" {
        fmt.Println(event.AsMessage().Text())
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    // chat has keys loaded and SetIdentity called (see Getting Started)
    chat.SetCacheKeys(true);
    chat.SetSigningKeys(participantSigningKeys); // all participants

    if (!string.IsNullOrEmpty(keyChangeB64))
    {
        // Verify the key change and retain its key in the cache
        chat.DecryptEvents(new[] { keyChangeB64 });
    }
    // Decrypt with the cached conversation key; verify against the stored signing keys
    var evt = chat.DecryptEvent(encodedEvent);
    if (evt.GetProperty("type").GetString() == "Message")
        Console.WriteLine(evt.GetProperty("content").GetProperty("text").GetString());
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    // chat has keys loaded and setIdentity called (see Getting Started)
    chat.setCacheKeys(true);
    chat.setSigningKeys(participantSigningKeys); // all participants

    if (keyChangeB64 != null && !keyChangeB64.isEmpty()) {
        // Verify the key change and retain its key in the cache
        chat.decryptEvents(List.of(keyChangeB64), null);
    }
    // Decrypt with the cached conversation key; verify against the stored signing keys
    JsonNode evt = chat.decryptEvent(encodedEvent, (Map<String, byte[]>) null, null);
    if ("Message".equals(evt.path("type").asText())) {
        System.out.println(evt.path("content").path("text").asText());
    }
    ```
  </Tab>
</Tabs>

Para mantener los mapas de claves en tus propias manos en su lugar, `extract_conversation_keys` descifra las claves de `conversation_key_change_event` y `decrypt_event` las acepta (junto con las claves de firma del remitente) como argumentos explícitos—un argumento explícito no vacío siempre gana sobre los almacenes.

Historial: [`GET /2/chat/conversations/{id}/events`](/x-api/chat/get-chat-conversation-events) + **`decrypt_events`** — consulta [Primeros pasos](/xchat/getting-started#6-receive-and-decrypt).

***

## Forma del payload (en vivo)

```json theme={null}
{
  "data": {
    "event_type": "chat.received",
    "event_uuid": "0f52b591-4b7e-4f13-92cd-30e6b2a3f18a",
    "payload": {
      "conversation_id": "1215441834412953600-1843439638876491776",
      "sender_id": "1843439638876491776",
      "encoded_event": "BASE64_ENCODED_MESSAGE_EVENT",
      "conversation_key_version": "1782945126642",
      "conversation_key_change_event": "BASE64_ENCODED_KEY_CHANGE_EVENT"
    }
  }
}
```

***

## Prácticas

* Verifica las firmas del webhook según los requisitos de la plataforma
* Configura los almacenes de sesión una vez: `set_signing_keys` para todos los participantes, `set_cache_keys(true)` para las claves de conversación
* Aplica los blobs de cambio de clave (mediante `decrypt_events`) antes de descifrar los mensajes dependientes
* Deduplica las entregas por `event_uuid` y los mensajes por el `message_id` firmado
