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

# Eventos de X Chat en tiempo real

> Recibe actividad cifrada de X Chat como chat.received, chat.sent y otros eventos mediante webhooks o el activity stream, y luego descifra los payloads con el Chat XDK.

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

| Capa               | Rol                                                                                                                                   |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| **X Activity API** | `GET /2/activity/stream`; `POST` / `GET` / `PUT` / `DELETE` `/2/activity/subscriptions` (consulta la seguridad 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 eventos privados de X Chat necesitan autorización para el usuario que monitorizas. Los adjuntos 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 API de Posts.

***

## 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 ofrece) |

***

## 1. Elige la entrega

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

**Suscripciones de Activity:** administra 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 alcances requeridos están definidos en la operación OpenAPI de cada ruta. Crear una suscripción de la X Activity API (XAA) requiere **autorización en contexto de usuario** (contexto de usuario OAuth 2.0 con los alcances relevantes, como `dm.read` para eventos de chat) del usuario cuya actividad monitorizas.

**Webhooks:** si terminas los eventos en tu endpoint HTTPS, registra un webhook con `POST /2/webhooks`, pasa los desafíos CRC, luego crea tus suscripciones de Activity con `POST /2/activity/subscriptions`, referenciando tu `webhook_id` (consulta las operaciones Webhooks y Activity en OpenAPI). El XDK de Python/TypeScript puede exponer helpers para webhooks y activity cuando la versión de tu 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 en contexto de usuario para crear suscripciones, token Bearer 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 tu consumer secret, en la forma JSON que espera tu producto de webhooks (típicamente `sha256=<base64>`).

***

## 3. Descifrar con el Chat XDK

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

Los snippets a continuación usan los dos almacenes de sesión **opcionales** para el handler más corto: `set_signing_keys` guarda las claves públicas de los participantes (obtenidas una vez del [endpoint public-keys](/x-api/chat/get-user-public-keys)), y `set_cache_keys(true)` mantiene la clave verificada de cada conversación, así que `decrypt_event` solo necesita el evento. Cuando un payload lleva `conversation_key_change_event`, pásalo primero por `decrypt_events`: eso verifica el cambio de clave y, con la caché activada, retiene su clave para la llamada a `decrypt_event`. ¿Prefieres no tener estado de 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`); otros 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 bajo tu control 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](/es/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 de webhooks 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 key-change (vía `decrypt_events`) antes de descifrar los mensajes dependientes
* Deduplica las entregas con `event_uuid` y los mensajes con el `message_id` firmado
