Skip to main content
X delivers 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 — create
  • GET /2/activity/subscriptions — list (paginated)
  • PUT /2/activity/subscriptions/{subscription_id} — update
  • DELETE /2/activity/subscriptions/{subscription_id} or DELETE /2/activity/subscriptions?ids= — delete
Request bodies and required scopes are defined in the OpenAPI operation for each route. Creating an X Activity API (XAA) subscription requires user-context authorization (OAuth 2.0 user context with the relevant scopes, such as 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.
Subscribe to 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 (GET crc_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.
To keep the key maps in your own hands instead, 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_keys for all participants, set_cache_keys(true) for conversation keys
  • Apply key-change blobs (via decrypt_events) before decrypting dependent messages
  • Deduplicate deliveries on event_uuid and messages on the signed message_id