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