### Install XChat SDK dependencies Source: https://docs.x.com/xchat/getting-started Commands and configuration files to install the necessary SDK packages for various environments. ```bash pip install chatxdk xdk ``` ```bash npm install @xdevplatform/chat-xdk @xdevplatform/xdk npm install juicebox-sdk # optional peer dependency; required for setup()/unlock() secure key backup ``` ```toml [dependencies] # chat-xdk-core is not yet on crates.io; use the git dependency chat-xdk-core = { git = "https://github.com/xdevplatform/chat-xdk" } # pin a release tag in production, e.g. tag = "vX.Y.Z" reqwest = { version = "0.12", features = ["blocking", "json"] } serde_json = "1" base64 = "0.22" # Required until thrift 0.24 is released on crates.io [patch.crates-io] thrift = { git = "https://github.com/apache/thrift.git", rev = "deb36fa409849de45973b04ffc3ce49d277ca90a" } ``` ```bash go get github.com/xdevplatform/chat-xdk/go/chatxdk ``` ```bash dotnet add package XDevPlatform.ChatXdk ``` ```xml com.x chatxdk x.y.z ``` -------------------------------- ### Install X Chat SDK dependencies Source: https://docs.x.com/xchat/building-ui-apps-with-wasm Install the required packages for the chat engine and secure key backup functionality. ```bash npm install @xdevplatform/chat-xdk npm install juicebox-sdk # required for setup() / unlock() secure key backup ``` -------------------------------- ### Register and Initialize Identity Source: https://docs.x.com/xchat/getting-started These examples demonstrate the flow of generating keypairs, registering them via the API, and setting the identity for the current session. ```python from xdk.chat.models import AddUserPublicKeyRequest registration = chat.generate_keypairs() pk = registration.public_key client.chat.add_user_public_key( "YOUR_USER_ID", AddUserPublicKeyRequest( public_key={ "identity_public_key_signature": pk.identity_public_key_signature, "public_key": pk.public_key, "public_key_fingerprint": pk.public_key_fingerprint, "registration_method": pk.registration_method, "signing_public_key": pk.signing_public_key, "signing_public_key_signature": pk.signing_public_key_signature, }, version=registration.version, generate_version=registration.generate_version, ), ) chat.setup("YOUR_PASSCODE") chat.set_identity("YOUR_USER_ID", str(registration.version or "1")) ``` ```typescript const registration = chat.generateKeypairs(); const pk = registration.publicKey; await client.chat.addUserPublicKey('YOUR_USER_ID', { public_key: { identity_public_key_signature: pk.identityPublicKeySignature, public_key: pk.publicKey, public_key_fingerprint: pk.publicKeyFingerprint, registration_method: pk.registrationMethod, signing_public_key: pk.signingPublicKey, signing_public_key_signature: pk.signingPublicKeySignature, }, version: registration.version, generate_version: registration.generateVersion, }); await chat.setup('YOUR_PASSCODE'); chat.setIdentity('YOUR_USER_ID', String(registration.version ?? '1')); ``` ```rust let registration = chat.generate_keypairs()?; let body = serde_json::to_value(®istration)?; let resp = http .post(format!("https://api.x.com/2/users/{user_id}/public_keys")) .header("Authorization", &auth) .json(&body) .send()?; if !resp.status().is_success() { anyhow::bail!("register keys: {}", resp.text()?); } let _blob = chat.export_keys()?; // store securely let key_version = registration.version.clone().unwrap_or_else(|| "1".into()); chat.set_identity(&user_id, &key_version); ``` ```go registration, err := chat.GenerateKeypairs() if err != nil { log.Fatal(err) } regJSON, _ := json.Marshal(registration) req, _ := http.NewRequest(http.MethodPost, "https://api.x.com/2/users/"+userID+"/public_keys", bytes.NewReader(regJSON)) req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { log.Fatal(err) } resp.Body.Close() privateKeys, _ := chat.ExportKeys() // store securely _ = privateKeys keyVersion := "1" if registration.Version != nil { keyVersion = *registration.Version } if err := chat.SetIdentity(userID, keyVersion); err != nil { log.Fatal(err) } ``` -------------------------------- ### Initialize Chat XDK with Existing Keys Source: https://docs.x.com/xchat/getting-started Examples for initializing the Chat XDK across multiple languages using either secure key backup or key blob imports. ```python import json from chat_xdk import Chat resp = client.chat.get_user_public_keys( "YOUR_USER_ID", public_key_fields=[ "public_key_version", "public_key", "signing_public_key", "identity_public_key_signature", "juicebox_config", ], ) record = resp.data[0] signing_key_version = str(record["public_key_version"]) chat = Chat(json.dumps(record["juicebox_config"])) chat.unlock("YOUR_PASSCODE") # recovers keys stored by setup() during first-time setup (step 3) # Or load a key blob instead of secure key backup: # chat.import_keys(blob, version=signing_key_version) chat.set_identity("YOUR_USER_ID", signing_key_version) ``` ```typescript import { createChat } from '@xdevplatform/chat-xdk'; const resp = await client.chat.getUserPublicKeys('YOUR_USER_ID', { publicKeyFields: [ 'public_key_version', 'public_key', 'signing_public_key', 'identity_public_key_signature', 'juicebox_config', ], }); const record = resp.data[0]; const signingKeyVersion = String(record.public_key_version); const chat = await createChat({ juiceboxConfig: JSON.stringify(record.juicebox_config), getAuthToken: async (realmId) => getRealmTokenFromYourBackend(realmId), }); await chat.unlock('YOUR_PASSCODE'); chat.setIdentity('YOUR_USER_ID', signingKeyVersion); ``` ```rust use base64::{engine::general_purpose::STANDARD as B64, Engine}; use chat_xdk_core::ChatCore; let chat = ChatCore::new(); let blob = B64.decode(std::env::var("PRIVATE_KEYS_B64")?)?; let signing_key_version = std::env::var("SIGNING_KEY_VERSION").unwrap_or_else(|_| "1".into()); chat.import_keys_with_version(&blob, &signing_key_version)?; chat.set_identity("YOUR_USER_ID", &signing_key_version); ``` ```go import "github.com/xdevplatform/chat-xdk/go/chatxdk" chat := chatxdk.New() defer chat.Close() blob, err := chatxdk.Base64ToBytes(os.Getenv("PRIVATE_KEYS_B64")) if err != nil { log.Fatal(err) } signingKeyVersion := os.Getenv("SIGNING_KEY_VERSION") if signingKeyVersion == "" { signingKeyVersion = "1" } if err := chat.ImportKeysWithVersion(blob, signingKeyVersion); err != nil { log.Fatal(err) } if err := chat.SetIdentity(myUserID, signingKeyVersion); err != nil { log.Fatal(err) } ``` ```csharp using ChatXdk; using var chat = new Chat(); var signingKeyVersion = Environment.GetEnvironmentVariable("SIGNING_KEY_VERSION") ?? "1"; chat.ImportKeys(Convert.FromBase64String( Environment.GetEnvironmentVariable("PRIVATE_KEYS_B64")!), signingKeyVersion); chat.SetIdentity(myUserId, signingKeyVersion); ``` ```java import com.x.chatxdk.Chat; String signingKeyVersion = Optional.ofNullable(System.getenv("SIGNING_KEY_VERSION")).orElse("1"); try (Chat chat = new Chat()) { chat.importKeys(Base64.getDecoder().decode(System.getenv("PRIVATE_KEYS_B64")), signingKeyVersion); chat.setIdentity(myUserId, signingKeyVersion); } ``` -------------------------------- ### Install Chat XDK via Package Managers Source: https://docs.x.com/xchat/xchat-xdk Commands to install the Chat XDK library for various supported programming languages. ```bash pip install chatxdk ``` ```bash npm install @xdevplatform/chat-xdk npm install juicebox-sdk # optional peer dependency; required for setup()/unlock() secure key backup ``` ```toml [dependencies] # chat-xdk-core is not yet on crates.io; use the git dependency. # It exports both ChatCore and the async secure-key-backup Chat type. chat-xdk-core = { git = "https://github.com/xdevplatform/chat-xdk" } # pin a release tag in production, e.g. tag = "vX.Y.Z" # Required until thrift 0.24 is released on crates.io [patch.crates-io] thrift = { git = "https://github.com/apache/thrift.git", rev = "deb36fa409849de45973b04ffc3ce49d277ca90a" } ``` ```bash go get github.com/xdevplatform/chat-xdk/go/chatxdk ``` ```bash dotnet add package XDevPlatform.ChatXdk ``` ```xml com.x chatxdk x.y.z ``` -------------------------------- ### Initialize API client with OAuth 2.0 token Source: https://docs.x.com/xchat/getting-started Setup the API client using a user-provided OAuth 2.0 access token. ```python from xdk import Client client = Client(access_token="YOUR_OAUTH2_USER_TOKEN") ``` ```typescript import { Client } from '@xdevplatform/xdk'; const client = new Client({ accessToken: 'YOUR_OAUTH2_USER_TOKEN' }); ``` ```rust let access_token = std::env::var("X_ACCESS_TOKEN")?; let http = reqwest::blocking::Client::new(); let auth = format!("Bearer {access_token}"); ``` ```go accessToken := os.Getenv("X_ACCESS_TOKEN") httpClient := &http.Client{Timeout: 30 * time.Second} ``` ```csharp using var http = new HttpClient(); http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( "Bearer", Environment.GetEnvironmentVariable("X_ACCESS_TOKEN")); ``` ```java String accessToken = System.getenv("X_ACCESS_TOKEN"); HttpClient http = HttpClient.newHttpClient(); ``` -------------------------------- ### Encrypt and Send Messages and Reactions Source: https://docs.x.com/xchat/xchat-xdk Examples for encrypting messages, replies, reactions, and general data across multiple languages. ```typescript const payload = chat.encryptMessage({ conversationId, text: 'Hello', // Optional: entities, attachments, shouldNotify, ttlMsec }); const body = { message_id: payload.messageId, encoded_message_create_event: payload.encryptedContent, encoded_message_event_signature: payload.encodedEventSignature, }; // POST body to /2/chat/conversations/{id}/messages // Preview derived from + embedded raw event so recipients can validate; // add replyToCkces: [...] when the original used an older key version const reply = chat.encryptReply({ conversationId, text: 'Sounds good', replyToEvent: originalEventB64, }); // Conversation and target derived from the raw event const add = chat.encryptAddReaction({ emoji: '👍', targetEvent: originalEventB64 }); const remove = chat.encryptRemoveReaction({ emoji: '👍', targetEvent: originalEventB64 }); const nameCt = chat.encrypt('Group title', rawConversationKey); const title = chat.decrypt(nameCt, rawConversationKey); ``` ```rust let payload = chat.encrypt_message(EncryptMessageParams::new(conversation_id, "Hello"))?; // Send body: payload.message_id → message_id, // payload.encrypted_content → encoded_message_create_event, // payload.encoded_event_signature → encoded_message_event_signature // Preview derived from + embedded raw event so recipients can validate; // set params.reply_to_ckces when the original used an older key version let reply = chat.encrypt_reply(EncryptReplyParams::new( conversation_id, "Sounds good", original_event_b64, ))?; // Conversation and target derived from the raw event let reaction = EncryptReactionParams::new(original_event_b64, "👍"); let add = chat.encrypt_add_reaction(&reaction)?; let remove = chat.encrypt_remove_reaction(&reaction)?; // conv_key: XChatConversationKey from extract_conversation_keys / decrypt_conversation_key let name_ct = chat.encrypt("Group title", &conv_key)?; let title = chat.decrypt(&name_ct, &conv_key)?; ``` ```go payload, err := chat.EncryptMessage(chatxdk.EncryptMessageParams{ ConversationID: conversationID, Text: "Hello", }) // Send body: payload.MessageID → message_id, // payload.EncryptedContent → encoded_message_create_event, // payload.EncodedEventSignature → encoded_message_event_signature // Preview derived from + embedded raw event so recipients can validate; // set ReplyToCkces when the original used an older key version reply, err := chat.EncryptReply(chatxdk.EncryptReplyParams{ ConversationID: conversationID, Text: "Sounds good", ReplyToEvent: originalEventB64, }) // Conversation and target derived from the raw event reaction := chatxdk.EncryptReactionParams{Emoji: "👍", TargetEvent: originalEventB64} add, err := chat.EncryptAddReaction(reaction) remove, err := chat.EncryptRemoveReaction(reaction) nameCt, err := chat.Encrypt("Group title", rawKey) title, err := chat.Decrypt(nameCt, rawKey) _ = payload _ = reply _ = add _ = remove _ = title _ = err ``` ```csharp var payload = chat.EncryptMessage(new EncryptMessageParams(conversationId, "Hello")); // Send body: payload.MessageId → message_id, // payload.EncryptedContent → encoded_message_create_event, // payload.EncodedEventSignature → encoded_message_event_signature // Preview derived from + embedded raw event so recipients can validate; // set ReplyToCkces when the original used an older key version var reply = chat.EncryptReply(new EncryptReplyParams(conversationId, "Sounds good", originalEventB64)); // Conversation and target derived from the raw event var reaction = new EncryptReactionParams(originalEventB64, "👍"); var add = chat.EncryptAddReaction(reaction); var remove = chat.EncryptRemoveReaction(reaction); var nameCt = chat.Encrypt("Group title", rawKey); var title = chat.Decrypt(nameCt, rawKey); ``` ```java SendPayload payload = chat.encryptMessage(new EncryptMessageParams(conversationId, "Hello")); // Send body: payload.messageId → message_id, // payload.encryptedContent → encoded_message_create_event, // payload.encodedEventSignature → encoded_message_event_signature // Preview derived from + embedded raw event so recipients can validate; // set replyToCkces when the original used an older key version SendPayload reply = chat.encryptReply(new EncryptReplyParams(conversationId, "Sounds good", originalEventB64)); ``` -------------------------------- ### GET /2/activity/subscriptions Source: https://docs.x.com/xchat/real-time-events Lists existing durable activity subscriptions. ```APIDOC ## GET /2/activity/subscriptions ### Description Retrieves a paginated list of all active durable subscriptions. ### Method GET ### Endpoint /2/activity/subscriptions ``` -------------------------------- ### GET /2/activity/stream Source: https://docs.x.com/xchat/real-time-events Establishes a stream to receive real-time X Chat activity events. ```APIDOC ## GET /2/activity/stream ### Description Listen for new X Chat messages and events in real time. ### Method GET ### Endpoint /2/activity/stream ``` -------------------------------- ### Configure and Decrypt Chat Events Source: https://docs.x.com/xchat/getting-started Sets up signing keys and enables key caching to facilitate event decryption. Includes examples for both batch processing and individual live event handling. ```python # Once per process: fill the signing-key store and enable the key cache def signing_keys_for(user_id: str) -> list[dict]: resp = client.chat.get_user_public_keys( user_id, public_key_fields=[ "public_key_version", "public_key", "signing_public_key", "identity_public_key_signature", ], ) return [ { "user_id": user_id, "public_key_version": r["public_key_version"], "public_key": r["signing_public_key"], "identity_public_key": r["public_key"], "identity_public_key_signature": r["identity_public_key_signature"], } for r in resp.data ] chat.set_signing_keys( signing_keys_for("YOUR_USER_ID") + signing_keys_for("RECIPIENT_USER_ID") ) chat.set_cache_keys(True) # Initial load or pagination: batch decrypt. Conversation keys are # extracted from the KeyChange events in the batch; per-event failures # are collected in result["errors"], never raised. result = chat.decrypt_events(all_events_b64) for dm in result["messages"]: event = dm["event"] if event["type"] == "Message" and event["content"]["content_type"] == "Text": print(event["sender_id"], event["content"]["text"], event["verified"]) # Live traffic: one event at a time def handle_payload(payload: dict): if payload.get("conversation_key_change_event"): # A rotation enters the key cache only after its signature # verifies, which is what decrypt_events does chat.decrypt_events([payload["conversation_key_change_event"]]) event = chat.decrypt_event(payload["encoded_event"]) # raises on failure if event["type"] == "Message" and event["content"]["content_type"] == "Text": print(event["sender_id"], event["content"]["text"], event["verified"]) ``` ```typescript // Once per process: fill the signing-key store and enable the key cache async function signingKeysFor(userId: string) { const resp = await client.chat.getUserPublicKeys(userId, { publicKeyFields: [ 'public_key_version', 'public_key', 'signing_public_key', 'identity_public_key_signature', ], }); return resp.data.map((r: { public_key_version: string; public_key: string; signing_public_key: string; identity_public_key_signature: string; }) => ({ userId, publicKeyVersion: r.public_key_version, publicKey: r.signing_public_key, identityPublicKey: r.public_key, identityPublicKeySignature: r.identity_public_key_signature, })); } chat.setSigningKeys([ ...(await signingKeysFor('YOUR_USER_ID')), ...(await signingKeysFor('RECIPIENT_USER_ID')), ]); chat.setCacheKeys(true); // Initial load or pagination: batch decrypt. Conversation keys are // extracted from the KeyChange events in the batch; per-event failures // are collected in result.errors, never thrown. const result = chat.decryptEvents(allEventsB64); for (const dm of result.messages) { if (dm.event.type === 'message' && dm.event.content?.contentType === 'text') { console.log(dm.event.senderId, dm.event.content.text, dm.event.verified); } } ``` -------------------------------- ### prepare_conversation_key_change Source: https://docs.x.com/xchat/xchat-xdk Generates a fresh conversation key, encrypts it for all participants, and signs the change. This is used for starting 1:1 conversations or rotating keys in existing conversations. ```APIDOC ## prepare_conversation_key_change ### Description Generates a fresh conversation key, encrypts it for every participant using their public keys, and signs the change. Returns a `PreparedConversationChange` object containing the raw key, participant keys, and action signatures. ### Parameters - **participants** (Array) - Required - List of participant objects containing user_id, public_key, and key_version. ``` -------------------------------- ### Initialize and manage keys in Python Source: https://docs.x.com/xchat/xchat-xdk Demonstrates secure key backup for clients and raw key blob persistence for servers or bots. ```python from chat_xdk import Chat # Secure key backup (client) chat = Chat(juicebox_config_json) chat.setup("YOUR_PASSCODE") # first time; generates keypairs # chat.unlock("YOUR_PASSCODE") # later sessions chat.set_identity(user_id, version) # version from add-public-key / get-public-keys response reg = chat.get_public_keys() # or registration fields from generate_keypairs # Key blob (server / bot) chat2 = Chat() chat2.import_keys(secret_blob, version) chat2.set_identity(user_id, version) blob = chat2.export_keys() # treat as a password ``` -------------------------------- ### Initialize and manage keys in Go Source: https://docs.x.com/xchat/xchat-xdk Uses ImportKeysWithVersion for server-side key management. ```go chat := chatxdk.New() defer chat.Close() // Prefer ImportKeys for servers; secure key backup unlock where supported keyBlob, _ := chatxdk.Base64ToBytes(privateKeysB64) if err := chat.ImportKeysWithVersion(keyBlob, version); err != nil { log.Fatal(err) } chat.SetIdentity(userID, version) publics, err := chat.GetPublicKeys() blob, err := chat.ExportKeys() _ = publics _ = blob _ = err ``` -------------------------------- ### Initialize and manage keys in Java Source: https://docs.x.com/xchat/xchat-xdk Uses try-with-resources for Chat instance management and standard key import/export methods. ```java try (Chat chat = new Chat()) { chat.importKeys(privateKeyBytes, version); chat.setIdentity(userId, version); var publics = chat.getPublicKeys(); byte[] blob = chat.exportKeys(); } ``` -------------------------------- ### GET /2/chat/conversations/{id}/events Source: https://docs.x.com/xchat/real-time-events Retrieves events for a specific chat conversation. ```APIDOC ## GET /2/chat/conversations/{id}/events ### Description Retrieves a list of events for a specific chat conversation. ### Method GET ### Endpoint /2/chat/conversations/{id}/events ``` -------------------------------- ### Manage Activity Streams and Subscriptions with XDK Source: https://docs.x.com/xchat/real-time-events Demonstrates initializing the XDK client for activity streams and creating event subscriptions for specific users. ```python 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"}, }) ``` ```typescript 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 ``` -------------------------------- ### Initialize and Use XChat XDK Source: https://docs.x.com/xchat/xchat-xdk Demonstrates the full lifecycle of initializing the chat session, configuring keys, and performing message operations. ```python from chat_xdk import Chat chat = Chat(juicebox_config_json) # or Chat() + import_keys(blob, version) chat.unlock("YOUR_PASSCODE") # Session defaults: identity for signing, stored signing keys for # verification, opt-in cache for conversation keys chat.set_identity(my_user_id, signing_key_version) chat.set_signing_keys(signing_keys) # all participants chat.set_cache_keys(True) # Batch-decrypt the backlog; senders verify against the stored keys result = chat.decrypt_events(raw_events) for dm in result["messages"]: ev = dm["event"] if ev["type"] == "Message": print(ev["sender_id"], ev["content"]["text"]) # Decrypt one live event with the cached conversation key event = chat.decrypt_event(one_event_b64) # Encrypt and sign as the session identity, under the cached key payload = chat.encrypt_message(event["conversation_id"], "Hi!") message_id = payload.message_id # SDK-generated; send as message_id ``` ```typescript import { createChat } from '@xdevplatform/chat-xdk'; const chat = await createChat({ juiceboxConfig: juiceboxConfigJson, getAuthToken: async (realmId) => getRealmToken(realmId), }); await chat.unlock('YOUR_PASSCODE'); // Session defaults: identity for signing, stored signing keys for // verification, opt-in cache for conversation keys chat.setIdentity(myUserId, signingKeyVersion); chat.setSigningKeys(signingKeys); // all participants chat.setCacheKeys(true); // Batch-decrypt the backlog; senders verify against the stored keys const result = chat.decryptEvents(rawEvents); for (const dm of result.messages) { if (dm.event.type === 'message') { console.log(dm.event.senderId, dm.event.content?.text); } } // Decrypt one live event with the cached conversation key const event = chat.decryptEvent(oneEventB64); // Encrypt and sign as the session identity, under the cached key const payload = chat.encryptMessage({ conversationId: event.conversationId!, text: 'Hi!' }); const messageId = payload.messageId; // SDK-generated; send as message_id ``` ```rust // chat_xdk_core::Chat + unlock(b"…").await, or ChatCore + import_keys_with_version // Session defaults: identity for signing, stored signing keys for // verification, opt-in cache for conversation keys chat.set_identity(my_user_id, signing_key_version); chat.set_signing_keys(signing_keys); // all participants chat.set_cache_keys(true); // Batch-decrypt the backlog; senders verify against the stored keys let result = chat.decrypt_events(&raw_events, &[]); for dm in &result.messages { if let Event::Message(msg) = &dm.event { println!("{}: {}", msg.meta.sender_id.as_deref().unwrap_or("?"), msg.text().unwrap_or("")); } } // Decrypt one live event with the cached conversation key let event = chat.decrypt_event(one_event_b64, &Default::default(), &[])?; // Encrypt and sign as the session identity, under the cached key let payload = chat.encrypt_message(EncryptMessageParams::new(conversation_id, "Hi!"))?; let message_id = payload.message_id; // SDK-generated; send as message_id ``` ```go chat := chatxdk.New() defer chat.Close() blob, _ := chatxdk.Base64ToBytes(privateKeysB64) _ = chat.ImportKeysWithVersion(blob, signingKeyVersion) // Session defaults: identity for signing, stored signing keys for // verification, opt-in cache for conversation keys chat.SetIdentity(myUserID, signingKeyVersion) _ = chat.SetSigningKeys(signingKeys) // all participants chat.SetCacheKeys(true) ``` -------------------------------- ### Initialize and manage keys in C# Source: https://docs.x.com/xchat/xchat-xdk Uses ImportKeys for key persistence and SetIdentity for session configuration. ```csharp using var chat = new Chat(); chat.ImportKeys(privateKeyBytes, version); // or secure key backup setup / unlock when config is available chat.SetIdentity(userId, version); var publics = chat.GetPublicKeys(); var blob = chat.ExportKeys(); ``` -------------------------------- ### POST /2/chat/media/upload/initialize Source: https://docs.x.com/xchat/media Initializes the media upload process. ```APIDOC ## POST /2/chat/media/upload/initialize ### Description Initializes a new media upload session. ### Method POST ### Endpoint /2/chat/media/upload/initialize ``` -------------------------------- ### GET /2/activity/stream Source: https://docs.x.com/xchat/real-time-events Retrieves an activity stream of events using an app Bearer token. This is often the simplest method for bots to receive events. ```APIDOC ## GET /2/activity/stream ### Description Retrieves an activity stream of events. Filter the results client-side for specific event types like `chat.received` or `chat.sent`. ### Method GET ### Endpoint /2/activity/stream ### Query Parameters - **backfill_minutes** (integer) - Optional - Number of minutes to backfill. - **start_time** (string) - Optional - Start time for the stream. - **end_time** (string) - Optional - End time for the stream. ``` -------------------------------- ### GET /2/chat/media/{conversation_id}/{media_hash_key} Source: https://docs.x.com/xchat/media Retrieves an encrypted media file from a specific conversation. The response body contains the ciphertext which must be decrypted using the conversation key corresponding to the event's key version. ```APIDOC ## GET /2/chat/media/{conversation_id}/{media_hash_key} ### Description Downloads an encrypted media attachment from a chat conversation. The returned data is ciphertext that requires decryption using the conversation key associated with the specific key version of the message. ### Method GET ### Endpoint https://api.x.com/2/chat/media/{conversation_id}/{media_hash_key} ### Parameters #### Path Parameters - **conversation_id** (string) - Required - The ID of the conversation, where colons should be replaced with hyphens. - **media_hash_key** (string) - Required - The unique hash key for the media attachment. ### Response #### Success Response (200) - **body** (binary) - The encrypted media content (ciphertext). ``` -------------------------------- ### Initialize and decrypt events in Go Source: https://docs.x.com/xchat/getting-started Sets up signing keys and processes decrypted messages from batch or live events. ```go // Once per instance: fill the signing-key store ([]SigningKeyEntry // from GET /2/users/{id}/public_keys) and enable the key cache if err := chat.SetSigningKeys(participantSigningKeys); err != nil { log.Fatal(err) } chat.SetCacheKeys(true) // Initial load: batch decrypt; per-event failures land in result.Errors result, err := chat.DecryptEvents(allEventsB64, nil) if err != nil { log.Fatal(err) } for _, dm := range result.Messages { if dm.Event.Type == "Message" { fmt.Println(dm.Event.AsMessage().Text()) } } // Live traffic: a rotation enters the key cache only after its // signature verifies, which is what DecryptEvents does if keyChange != "" { chat.DecryptEvents([]string{keyChange}, nil) } event, err := chat.DecryptEvent(encodedEvent, nil, nil) if err == nil && event.Type == "Message" { fmt.Println(event.AsMessage().Text()) } ``` -------------------------------- ### Initialize and manage keys in Rust Source: https://docs.x.com/xchat/xchat-xdk Supports both secure key backup and raw key blob operations. ```rust // chat_xdk_core::Chat: async secure key backup unlock, or ChatCore + import_keys chat.setup(b"YOUR_PASSCODE").await?; // chat.unlock(b"YOUR_PASSCODE").await?; chat.set_identity(user_id, version); let publics = chat.get_public_keys()?; let blob = chat.export_keys()?; chat.import_keys_with_version(&blob, version)?; ``` -------------------------------- ### Initialize and decrypt events in Java Source: https://docs.x.com/xchat/getting-started Sets up signing keys and processes JSON-based event data. ```java // Once per instance: fill the signing-key store (SigningKeyEntry list // from GET /2/users/{id}/public_keys) and enable the key cache chat.setSigningKeys(participantSigningKeys); chat.setCacheKeys(true); // Initial load: batch decrypt; per-event failures land in result.errors DecryptEventsResult result = chat.decryptEvents(allEventsB64, null); for (DecryptedMessage dm : result.messages) { if ("Message".equals(dm.event.path("type").asText())) { System.out.println(dm.event.path("content").path("text").asText()); } } // Live traffic: a rotation enters the key cache only after its // signature verifies, which is what decryptEvents does if (keyChangeB64 != null && !keyChangeB64.isEmpty()) { chat.decryptEvents(List.of(keyChangeB64), null); } JsonNode evt = chat.decryptEvent(encodedEvent, (Map) null, null); if ("Message".equals(evt.path("type").asText())) { System.out.println(evt.path("content").path("text").asText()); } ``` -------------------------------- ### Set up conversation keys in Rust Source: https://docs.x.com/xchat/getting-started Prepare conversation key change parameters and perform an HTTP request to add keys in Rust. ```rust // public_key_inputs: Vec from GET public keys // (user_id, public_key, key_version ← public_key_version) // New 1:1; set params.conversation_id = Some(id) to rotate later let prepared = chat.prepare_conversation_key_change( ConversationKeyChangeParams::new(public_key_inputs), )?; let participant_keys: Vec<_> = prepared .participant_keys .iter() .map(|pk| { serde_json::json!({ "user_id": pk.user_id, "encrypted_conversation_key": pk.encrypted_key, "public_key_version": pk.public_key_version, }) }) .collect(); let action_signatures: Vec<_> = prepared .action_signatures .iter() .map(|sig| { serde_json::json!({ "message_id": sig.message_id, "encoded_message_event_detail": sig.encoded_message_event_detail, "message_event_signature": { "signature": sig.signature, "public_key_version": sig.public_key_version, "signature_version": sig.signature_version, }, }) }) .collect(); let body = serde_json::json!({ "conversation_key_version": prepared.conversation_key_version, "conversation_participant_keys": participant_keys, "action_signatures": action_signatures, }); let resp: serde_json::Value = http .post(format!("https://api.x.com/2/chat/conversations/{recipient_id}/keys")) .header("Authorization", &auth) .json(&body) .send()? .json()?; // Canonical id for later requests let conversation_id = resp["data"]["conversation_id"].as_str().unwrap().to_string(); // conversation_key is Option; encrypt_message wants owned bytes let conv_key = prepared.conversation_key.expect("key present").to_bytes(); let conv_key_version = prepared.conversation_key_version; ``` -------------------------------- ### Set up conversation keys in JavaScript Source: https://docs.x.com/xchat/getting-started Prepare a conversation key change and add keys to a recipient using the JavaScript client. ```javascript // Omit conversationId for a new 1:1; pass the id to rotate later const prepared = chat.prepareConversationKeyChange({ publicKeys: [ await publicKeyInput('YOUR_USER_ID'), await publicKeyInput('RECIPIENT_USER_ID'), ], }); const resp = await client.chat.addConversationKeys('RECIPIENT_USER_ID', { conversation_key_version: prepared.conversationKeyVersion, conversation_participant_keys: prepared.participantKeys.map((pk) => ({ user_id: pk.userId, encrypted_conversation_key: pk.encryptedKey, public_key_version: pk.publicKeyVersion, })), action_signatures: prepared.actionSignatures.map((sig) => ({ message_id: sig.messageId, encoded_message_event_detail: sig.encodedMessageEventDetail, message_event_signature: { signature: sig.signature, public_key_version: sig.publicKeyVersion, signature_version: sig.signatureVersion, }, })), }); const conversationId = resp.data.conversation_id; // canonical id for later requests const sequenceId = resp.data.sequence_id; const convKey = prepared.conversationKey; const convKeyVersion = prepared.conversationKeyVersion; ``` -------------------------------- ### Perform chat operations in Java Source: https://docs.x.com/xchat/xchat-xdk Demonstrates session initialization, batch decryption, and message encryption using the Java SDK. ```java try (Chat chat = new Chat()) { chat.importKeys(privateKeyBytes, signingKeyVersion); // Session defaults: identity for signing, stored signing keys for // verification, opt-in cache for conversation keys chat.setIdentity(myUserId, signingKeyVersion); chat.setSigningKeys(signingKeys); // all participants chat.setCacheKeys(true); // Batch-decrypt the backlog; senders verify against the stored keys DecryptEventsResult result = chat.decryptEvents(rawEvents, null); for (DecryptedMessage dm : result.messages) { if ("Message".equals(dm.event.path("type").asText())) { System.out.println(dm.event.path("content").path("text").asText()); } } // Decrypt one live event with the cached conversation key JsonNode event = chat.decryptEvent(oneEventB64, (Map) null, null); String conversationId = event.path("conversation_id").asText(); // Encrypt and sign as the session identity, under the cached key SendPayload payload = chat.encryptMessage(new EncryptMessageParams(conversationId, "Hi!")); String messageId = payload.messageId; // SDK-generated; send as message_id } ``` -------------------------------- ### Initialize and manage keys in TypeScript Source: https://docs.x.com/xchat/xchat-xdk Uses secure key backup for identity management; raw key export/import is not supported in JS/WASM environments. ```typescript import { createChat } from '@xdevplatform/chat-xdk'; const chat = await createChat({ juiceboxConfig: juiceboxConfigJson, getAuthToken: async (realmId) => getRealmToken(realmId), }); await chat.setup('YOUR_PASSCODE'); // await chat.unlock('YOUR_PASSCODE'); chat.setIdentity(userId, version); const publics = chat.getPublicKeys(); // JS/WASM stores keys only through secure key backup; there is no raw key // export/import here. For key-blob persistence, use a native binding. ``` -------------------------------- ### Prepare and register conversation keys in Python Source: https://docs.x.com/xchat/getting-started Fetches user public keys and registers a new conversation key with the API. Ensure all fetched keys are verified before use to prevent identity substitution. ```python def public_key_input(user_id: str) -> dict: r = client.chat.get_user_public_keys( user_id, public_key_fields=["public_key_version", "public_key"] ).data[0] return {"user_id": user_id, "public_key": r["public_key"], "key_version": r["public_key_version"]} prepared = chat.prepare_conversation_key_change( [public_key_input("YOUR_USER_ID"), public_key_input("RECIPIENT_USER_ID")], # conversation_id=None for a new 1:1; pass the id to rotate later ) resp = client.chat.add_conversation_keys( "RECIPIENT_USER_ID", { "conversation_key_version": prepared["conversation_key_version"], "conversation_participant_keys": [ { "user_id": pk["user_id"], "encrypted_conversation_key": pk["encrypted_key"], "public_key_version": pk["public_key_version"], } for pk in prepared["participant_keys"] ], "action_signatures": [ { "message_id": sig["message_id"], "encoded_message_event_detail": sig["encoded_message_event_detail"], "message_event_signature": { "signature": sig["signature"], "public_key_version": sig["public_key_version"], "signature_version": sig["signature_version"], }, } for sig in prepared["action_signatures"] ], }, ) conversation_id = resp.data["conversation_id"] # canonical id for later requests sequence_id = resp.data["sequence_id"] conv_key = prepared["conversation_key"] conv_key_version = prepared["conversation_key_version"] ``` -------------------------------- ### Prepare and submit conversation keys Source: https://docs.x.com/xchat/getting-started Prepares the conversation key change parameters and sends the resulting payload to the X API. ```go // KeyVersion comes from the public_key_version field on each record prepared, err := chat.PrepareConversationKeyChange(chatxdk.ConversationKeyChangeParams{ PublicKeys: []chatxdk.PublicKeyInput{ {UserID: myUserID, PublicKey: myIdentityPubB64, KeyVersion: myKeyVersion}, {UserID: recipientID, PublicKey: theirIdentityPubB64, KeyVersion: theirKeyVersion}, }, // ConversationID empty for a new 1:1; pass the id to rotate later }) var parts []map[string]string for _, pk := range prepared.ParticipantKeys { parts = append(parts, map[string]string{ "user_id": pk.UserID, "encrypted_conversation_key": pk.EncryptedKey, "public_key_version": pk.PublicKeyVersion, }) } var sigs []map[string]any for _, sig := range prepared.ActionSignatures { sigs = append(sigs, map[string]any{ "message_id": sig.MessageID, "encoded_message_event_detail": sig.EncodedMessageEventDetail, "message_event_signature": map[string]string{ "signature": sig.Signature, "public_key_version": sig.PublicKeyVersion, "signature_version": sig.SignatureVersion, }, }) } body, _ := json.Marshal(map[string]any{ "conversation_key_version": prepared.ConversationKeyVersion, "conversation_participant_keys": parts, "action_signatures": sigs, }) req, _ := http.NewRequest(http.MethodPost, "https://api.x.com/2/chat/conversations/"+recipientID+"/keys", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) // Response data.conversation_id is the canonical id for later requests _ = resp convKey := prepared.ConversationKey convKeyVersion := prepared.ConversationKeyVersion ``` ```csharp // KeyVersion comes from the public_key_version field on each record var prepared = chat.PrepareConversationKeyChange(new ConversationKeyChangeParams(new[] { new PublicKeyInput { UserId = myUserId, PublicKey = myPk, KeyVersion = myVer }, new PublicKeyInput { UserId = recipientId, PublicKey = theirPk, KeyVersion = theirVer }, })); // ConversationId null for a new 1:1; set it to rotate later var keysBody = new { conversation_key_version = prepared.ConversationKeyVersion, conversation_participant_keys = prepared.ParticipantKeys.Select(pk => new { user_id = pk.UserId, encrypted_conversation_key = pk.EncryptedKey, public_key_version = pk.PublicKeyVersion, }), action_signatures = prepared.ActionSignatures.Select(sig => new { message_id = sig.MessageId, encoded_message_event_detail = sig.EncodedMessageEventDetail, message_event_signature = new { signature = sig.Signature, public_key_version = sig.PublicKeyVersion, signature_version = sig.SignatureVersion, }, }), }; var json = System.Text.Json.JsonSerializer.Serialize(keysBody); using var content = new StringContent(json, Encoding.UTF8, "application/json"); using var resp = await http.PostAsync( $"https://api.x.com/2/chat/conversations/{Uri.EscapeDataString(recipientId)}/keys", content); resp.EnsureSuccessStatusCode(); var data = System.Text.Json.JsonDocument.Parse(await resp.Content.ReadAsStringAsync()) .RootElement.GetProperty("data"); string conversationId = data.GetProperty("conversation_id").GetString()!; // canonical id byte[] convKey = prepared.ConversationKey!; string convKeyVersion = prepared.ConversationKeyVersion; ``` -------------------------------- ### SDK Configuration Methods Source: https://docs.x.com/xchat/getting-started Methods to configure the SDK for event decryption and verification. ```APIDOC ## set_signing_keys(entries) ### Description Registers the public signing keys for participants to allow the SDK to verify message authorship. ## set_cache_keys(enabled) ### Description Enables or disables the caching of signature-verified conversation keys to simplify subsequent decryption calls. ``` -------------------------------- ### Set session identity and import keys Source: https://docs.x.com/xchat/troubleshooting Initialize the session by importing keys and setting the user identity before performing encryption or decryption operations. ```python chat.unlock(passcode) # or: chat.import_keys(blob) chat.set_identity(my_user_id, signing_key_version) ``` ```typescript await chat.unlock(passcode); chat.setIdentity(myUserId, signingKeyVersion); ``` ```rust chat.import_keys(&blob)?; chat.set_identity(&my_user_id, &signing_key_version); ``` ```go blob, _ := chatxdk.Base64ToBytes(privateKeysB64) _ = chat.ImportKeys(blob) _ = chat.SetIdentity(myUserID, signingKeyVersion) ``` ```csharp chat.ImportKeys(blobBytes); chat.SetIdentity(myUserId, signingKeyVersion); ``` ```java chat.importKeys(blobBytes); chat.setIdentity(myUserId, signingKeyVersion); ``` -------------------------------- ### Perform chat operations in Go Source: https://docs.x.com/xchat/xchat-xdk Demonstrates batch decryption, live event decryption, and message encryption using the Go SDK. ```go // Batch-decrypt the backlog; senders verify against the stored keys result, err := chat.DecryptEvents(rawEvents, nil) for _, dm := range result.Messages { if dm.Event.Type == "Message" { fmt.Println(dm.Event.AsMessage().Text()) } } // Decrypt one live event with the cached conversation key event, err := chat.DecryptEvent(oneEventB64, nil, nil) msg := event.AsMessage() // nil unless event.Type == "Message" // Encrypt and sign as the session identity, under the cached key payload, err := chat.EncryptMessage(chatxdk.EncryptMessageParams{ ConversationID: *msg.ConversationID, Text: "Hi!", }) messageID := payload.MessageID // SDK-generated; send as message_id _ = messageID _ = err ``` -------------------------------- ### prepare_group_create Source: https://docs.x.com/xchat/xchat-xdk Prepares the necessary keys and signatures for creating a new group conversation. ```APIDOC ## prepare_group_create ### Description Prepares conversation keys for group creation. Returns two action signatures that must be included in the POST body. ``` -------------------------------- ### POST /2/activity/subscriptions Source: https://docs.x.com/xchat/real-time-events Creates a new durable activity subscription. Requires user-context authorization. ```APIDOC ## POST /2/activity/subscriptions ### Description Creates a new durable subscription to monitor specific user activities. Requires OAuth 2.0 user-context authorization with relevant scopes (e.g., `dm.read`). ### Method POST ### Endpoint /2/activity/subscriptions ``` -------------------------------- ### verify_key_binding Source: https://docs.x.com/xchat/xchat-xdk Verifies the binding of a fetched public key before using it for encryption. ```APIDOC ## verify_key_binding ### Description Validates the identity, signing, and signature of a fetched public key record to prevent identity substitution attacks. ```