chat.received, chat.sent, and related X Chat activity with ciphertext in the payload. Decrypt with the Chat XDK.
Private X Chat event types need authorization for the user you monitor. Encrypted X Chat file attachments use
media_hash_key and X Chat media download—not Post API expansions=attachments.media_keys / media.fields=variants.
Event types
1. Choose delivery
Activity stream (often simplest for bots):GET /2/activity/stream with an app Bearer token (optional backfill_minutes, start_time, end_time per OpenAPI). Filter client-side for chat.received / chat.sent.
Activity subscriptions: manage durable subscriptions with:
POST /2/activity/subscriptions— createGET /2/activity/subscriptions— list (paginated)PUT /2/activity/subscriptions/{subscription_id}— updateDELETE /2/activity/subscriptions/{subscription_id}orDELETE /2/activity/subscriptions?ids=— delete
dm.read for chat events) for the user whose activity you monitor.
Webhooks: if you terminate events on your HTTPS endpoint, register a webhook with POST /2/webhooks, pass CRC challenges, then create your activity subscriptions with POST /2/activity/subscriptions, referencing your webhook_id (see Webhooks and Activity operations in OpenAPI). Python/TypeScript XDK may expose helpers for webhooks and activity when your SDK version includes them.
- Python
- TypeScript
chat.sent as well if you need outbound copies. Other languages: call the same /2/activity/* HTTPS routes directly (user-context token to create subscriptions, app Bearer token for the stream).
2. CRC (webhooks only)
If you use webhooks, respond to Challenge-Response Checks (GETcrc_token) with HMAC-SHA256 of the token using your consumer secret, in the JSON shape your webhook product expects (typically sha256=<base64>).
3. Decrypt with the Chat XDK
Live fields:payload.encoded_event, optional payload.conversation_key_change_event. Deduplicate deliveries on event_uuid; deduplicate messages on the message_id carried in the decrypted event—it is part of the signed content, while sequence ids are backend-assigned, unsigned metadata.
The snippets below use the two optional session stores for the shortest handler: set_signing_keys holds the participants’ public keys (fetched once from the public-keys endpoint), and set_cache_keys(true) keeps each conversation’s verified key, so decrypt_event needs only the event. When a payload carries conversation_key_change_event, run it through decrypt_events first: that verifies the key change and, with caching on, retains its key for the decrypt_event call. Prefer no instance state? Pass the keys per call instead—see the note at the end of this section.
JavaScript uses camelCase event types (message); other bindings use "Message" and snake_case fields.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
extract_conversation_keys decrypts the keys from conversation_key_change_event and decrypt_event accepts them (and the sender’s signing keys) as explicit arguments—an explicit non-empty argument always wins over the stores.
History: GET /2/chat/conversations/{id}/events + decrypt_events — see Getting Started.
Payload shape (live)
Practices
- Verify webhook signatures per platform requirements
- Set the session stores once:
set_signing_keysfor all participants,set_cache_keys(true)for conversation keys - Apply key-change blobs (via
decrypt_events) before decrypting dependent messages - Deduplicate deliveries on
event_uuidand messages on the signedmessage_id