How groups differ from 1:1
Crypto is still: Chat XDK for keys and payloads; X API to create the group, publish participant key wraps, send messages, and load events.
Create the group and establish keys
- Mint the group id with
POST /2/chat/conversations/group/initialize— the response’sdata.conversation_idis the g-prefixed id you use everywhere below. - Load each member’s identity public key and
public_key_version(GETpublic-key routes under Encryption keys;GET /2/users/public_keysfetches several users in one request). Verify each record withverify_key_bindingbefore using it (see the warning in Getting Started). - Run
prepare_group_createonce, with all members (including yourself), the g-prefixed id, and the member/admin id lists. One call generates the conversation key, wraps it for every member, and signs the create — it returns two action signatures (the conversation-key change and the group create). POST /2/chat/conversations/groupwith the group members/admins,conversation_key_version,conversation_participant_keys(SDKencrypted_key→ APIencrypted_conversation_key), and bothaction_signatures. Validation failures come back as stable, human-readable messages, for example"Too many members: adding these members would exceed the allowed group size."or"Cannot add all members: one or more of the requested members cannot be added to this conversation.".- Keep the raw conversation key and version for encrypt/decrypt.
prepare_group_create signs the title and avatar_url you pass in and embeds them verbatim in the group-create event. The server matches those against your request, so the group_name / group_avatar_url values in the POST body must be byte-identical to what you passed the SDK — otherwise the call fails signature validation.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
message_id, encoded_message_event_detail, nested message_event_signature) is the same as the keys POST in Getting Started — conversation keys.
When membership changes, call prepare_group_members_change with the new member ids plus the current roster (members, admins, pending members, and the current title/avatar/TTL if set). It rotates the conversation key and, like group create, returns two action signatures — POST all of it to add members (POST /2/chat/conversations/{id}/members). Then expect key-change traffic: treat it like key rotation in Getting Started (extract_conversation_keys / decrypt_events, then encrypt with the latest version).
Because prepare_group_members_change generates a fresh conversation key wrapped only to the roster you pass, new members receive the new key version and cannot decrypt messages sent under earlier versions. The reverse is not true: rotation never revokes access to earlier versions — anyone who already holds an old key can still read the messages encrypted under it. If you suspect a conversation key was exposed, rotate with prepare_conversation_key_change; this protects future messages only.
Encrypted group metadata
Some conversation fields (for example display name or avatar URL) may arrive encrypted under the conversation key. That is notencrypt_message; it is the generic Chat XDK encrypt / decrypt pair (UTF-8 string in, base64 ciphertext out, with the raw conversation key).
Whether a given field is stored encrypted is decided by the client that writes it: prepare_group_create signs and sends the title exactly as you provide it (the conversation key does not exist until that call generates it, so a create-time title cannot be encrypted under it). When you read a conversation whose fields are ciphertext, decrypt them with decrypt and the key version that was active when the field was written.
- Python
- TypeScript
- Rust
- Go
- C#
- Java
Messages and events
Sending and receiving in a group is the same as 1:1 once you have the raw conversation key:- Send:
encrypt_message→ send-message API (Getting Started) - Receive: events API or real-time delivery →
decrypt_event/decrypt_events - Media: Media with the group conversation id
Checklist
- Mint the g-prefixed id with
POST /2/chat/conversations/group/initialize prepare_group_createwith every member; POST participant key wraps and both action signatures toPOST /2/chat/conversations/group- Cache raw key + version; update on key-change events
- On membership changes,
prepare_group_members_change(two signatures) →POST /2/chat/conversations/{id}/members - Decrypt group metadata with
decryptwhen fields are ciphertext - Send/receive with the same patterns as 1:1