Prerequisites
- Developer account and an app configured for OAuth 2.0
- User access token with
dm.read,dm.write,tweet.read, andusers.read
1. Install dependencies
- Python
- TypeScript
- Rust
- Go
- C#
- Java
chatxdk; import it as chat_xdk. Requires Python 3.10+.- Python
- TypeScript
- Rust
- Go
- C#
- Java
2. Initialize the Chat XDK with existing keys
This step loads keys you already have—use it when this identity completed first-time setup before:- Secure key backup: construct the SDK with the
juicebox_configfrom your public-key record, thenunlockwith your passcode to recover the private keys (for example, on a new device). - Key blob:
import_keyswith a blob you previously exported viaexport_keys.
public_key_version on your record).
Setting up for the first time? Construct the SDK the same way but skip unlock/import_keys, and continue to step 3 to create, back up, and register your keys.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
export_keys / import_keys). Client apps often use secure key backup (setup / unlock with a passcode). See the Chat XDK reference for both paths.
Bringing your own keys?
import_keys only accepts the opaque blob produced by export_keys from the Chat XDK—it is a versioned, private serialization of the full key state, not raw or PEM-encoded P-256 keys. You cannot construct this blob yourself: generate keys through generate_keypairs (step 3), export the blob once, and store it base64-encoded. Hand-crafted or modified blobs fail to import.3. Create and register keys (first-time setup)
Skip this step if you loaded existing keys in step 2. Otherwise, one-time setup for a new identity does three things:- Create the keypairs —
generate_keypairsproduces the identity and signing keypairs. - Register the public keys — POST the registration payload to the add-public-key endpoint so others can encrypt to you and verify your signatures.
- Store the private keys —
setupwith a passcode writes them to secure key backup (clients), orexport_keysreturns a key blob for you to store securely (servers and bots).
- Python
- TypeScript
- Rust
- Go
- C#
- Java
4. Set up conversation keys
Callprepare_conversation_key_change with your user ID, your signing key version, and every participant’s identity public key. One call generates a fresh conversation key, encrypts it for each participant, and signs the change. POST the result to the add conversation keys endpoint (POST /2/chat/conversations/{id}/keys)—the body needs conversation_key_version, conversation_participant_keys (SDK encrypted_key → API encrypted_conversation_key), and action_signatures (required; the API rejects the call without them). Keep the raw conversation key for sending.
The response returns the canonical conversation id (data.conversation_id—the hyphen-joined pair for a 1:1, or the g-prefixed id for a group) and the key change’s data.sequence_id. Use that returned id for subsequent requests instead of reconstructing it client-side. The same call also rotates keys later: pass the existing conversation id to prepare_conversation_key_change and POST with the newer key version. Rotate when you suspect the conversation key was exposed—rotation protects future messages only; messages encrypted under earlier key versions stay readable to anyone who holds those versions.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
5. Send a message
Encrypt with the raw conversation key bytes. On the send request, map:
Use a hyphenated conversation id in the URL path when the API requires it (
: → -). The SDK itself is flexible: encrypt_message and encrypt_reply accept the id in any form you hold—A:B from events, A-B from listings or URL paths (in either order), or just the recipient’s user id—and canonicalize it before signing. Group ids (prefixed with g) pass through unchanged.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
6. Receive and decrypt
Use webhooks or the activity stream for live traffic, or page conversation events for history.- Live payload fields:
encoded_event, optionalconversation_key_change_event - History:
GET /2/chat/conversations/{id}/events— preferdecrypt_eventson all events plusmeta.conversation_key_events - Pass the sender’s public keys into decrypt for signature verification (map API fields into
SigningKeyEntry; see Chat XDK) - JavaScript uses camelCase event types (
message); other languages use"Message"and snake_case fields in JSON
- Python
- TypeScript
- Rust
- Go
- C#
- Java
Best practices
- Cache raw conversation keys and sender public keys; refresh on signature verification failures
- Deduplicate live deliveries with
event_uuid - Page event history until pagination is complete so key-change metadata is not missed
- Do not log passcodes, private keys, or message plaintext in production
- In web apps, keep OAuth tokens (and key backup realm token minting) on a server; prefer holding private keys only in the client Chat XDK
Next steps
Chat XDK
Methods and types for all language bindings
Media
Encrypted images and file attachments
Groups
Multi-participant conversations and metadata
Real-time events
Webhooks and activity delivery