> ## Documentation Index
> Fetch the complete documentation index at: https://docs.x.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 그룹 대화

> 공유 대화 키, 암호화된 제목, 멤버 관리, 서명된 메시지를 갖춘 다자간 X Chat 그룹 대화를 생성합니다.

그룹 채팅은 1:1 X Chat과 **동일한 암호화 모델**을 사용합니다: 멤버가 공유하는 하나의 **대화 키**를 각 멤버의 **신원 공개 키**로 래핑하고, Chat XDK가 메시지를 암호화하고 서명합니다. 달라지는 것은 **멤버십**, **대화를 생성하는 방식**, 그리고 종종 대화의 **암호화된 제목/아바타** 필드입니다.

1:1 흐름은 [시작하기](/xchat/getting-started)에 있습니다. 엔드포인트 세부 사항은 **API 참조 → 대화 및 메시지** 아래에 있습니다.

***

## 그룹과 1:1의 차이점

| 주제    | 1:1                    | 그룹                                   |
| :---- | :--------------------- | :----------------------------------- |
| 식별    | 경로에서 종종 상대방 사용자 ID로 지정 | 대화 ID가 일반적으로 `g`로 시작                 |
| 생성    | 사용자에 대한 키 + 메시징        | 그룹 생성 / 초기화 API, 그런 다음 키             |
| 참가자   | 나 + 상대방 한 명            | 여러 사용자; 멤버십이 변경 가능                   |
| 메타데이터 | 최소                     | 이름, 아바타 등은 **암호문**일 수 있음 (대화 키로 복호화) |
| 키 교체  | 덜 빈번                   | 사람이 참여하거나 떠날 때 흔함                    |

암호화는 여전히: 키와 페이로드에는 **Chat XDK**를, 그룹 생성, 참가자 키 래핑 게시, 메시지 전송, 이벤트 로드에는 **X API**를 사용합니다.

***

## 그룹 생성 및 키 설정

1. `POST /2/chat/conversations/group/initialize`로 그룹 ID를 발급합니다 — 응답의 `data.conversation_id`가 이후 모든 곳에서 사용하는 g-접두사 ID입니다.
2. 각 멤버의 신원 공개 키와 `public_key_version`을 로드합니다 (**암호화 키** 아래 `GET` 공개 키 경로; [`GET /2/users/public_keys`](/x-api/users/get-public-keys-for-multiple-users)는 한 요청으로 여러 사용자를 가져옴). 사용하기 전에 `verify_key_binding`으로 각 레코드를 검증하세요 ([시작하기](/xchat/getting-started#4-set-up-conversation-keys)의 경고 참조).
3. **모든** 멤버(자신 포함), g-접두사 ID, 멤버/관리자 ID 목록으로 \*\*`prepare_group_create`\*\*를 한 번 실행합니다. 한 번의 호출로 대화 키를 생성하고, 모든 멤버에 대해 래핑하고, 생성에 서명합니다 — **두 개**의 action signature(대화 키 변경과 그룹 생성)를 반환합니다.
4. 그룹 멤버/관리자, `conversation_key_version`, `conversation_participant_keys`(SDK **`encrypted_key`** → API **`encrypted_conversation_key`**), 그리고 **두 개 모두**의 `action_signatures`와 함께 `POST /2/chat/conversations/group`을 호출합니다. 검증 실패는 안정적이고 사람이 읽을 수 있는 메시지로 반환됩니다. 예: `"Too many members: adding these members would exceed the allowed group size."` 또는 `"Cannot add all members: one or more of the requested members cannot be added to this conversation."`.
5. 암호화/복호화를 위해 **원시** 대화 키와 **버전**을 보관하세요.

`prepare_group_create`에 전달하는 `title`과 `avatar_url`은 서명되어 그룹 생성 이벤트에 그대로 임베드되며, 서버는 이를 요청과 대조합니다 — 그래서 POST 본문의 `group_name` / `group_avatar_url` 값은 SDK에 전달한 것과 **바이트 단위로 동일**해야 합니다. 그렇지 않으면 호출이 서명 검증에 실패합니다.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    prepared = chat.prepare_group_create(
        "YOUR_USER_ID", signing_key_version, member_public_keys,
        group_id,  # g-prefixed id from POST /2/chat/conversations/group/initialize
        member_ids, admin_ids, title="Project team",
    )
    # POST /2/chat/conversations/group with group_members, group_admins,
    # conversation_key_version, conversation_participant_keys, and BOTH
    # entries of prepared["action_signatures"]
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const prepared = chat.prepareGroupCreate({
      senderId: myUserId, signingKeyVersion, publicKeys: memberPublicKeys,
      conversationId: groupId, // g-prefixed id from POST /2/chat/conversations/group/initialize
      memberIds, adminIds, title: 'Project team',
    });
    // prepared.actionSignatures has two entries — send both
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let mut params = GroupCreateParams::new(
        &sender_id, &signing_key_version, member_public_keys,
        &group_id, member_ids, admin_ids,
    );
    params.title = Some("Project team".into());
    let prepared = chat.prepare_group_create(params)?;
    // prepared.action_signatures has two entries — send both
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    prepared, err := chat.PrepareGroupCreate(chatxdk.GroupCreateParams{
        SenderID: myUserID, SigningKeyVersion: signingKeyVersion,
        PublicKeys: memberPublicKeys, ConversationID: groupID,
        MemberIDs: memberIDs, AdminIDs: adminIDs, Title: "Project team",
    })
    // prepared.ActionSignatures has two entries — send both
    _ = prepared
    _ = err
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    var prepared = chat.PrepareGroupCreate(new GroupCreateParams {
        SenderId = myUserId, SigningKeyVersion = signingKeyVersion,
        PublicKeys = memberPublicKeys, ConversationId = groupId,
        MemberIds = memberIds, AdminIds = adminIds, Title = "Project team",
    });
    // prepared.ActionSignatures has two entries — send both
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    GroupCreateParams params = new GroupCreateParams();
    params.senderId = myUserId;
    params.signingKeyVersion = signingKeyVersion;
    params.publicKeys = memberPublicKeys;
    params.conversationId = groupId;
    params.memberIds = memberIds;
    params.adminIds = adminIds;
    params.title = "Project team";
    PreparedConversationChange prepared = chat.prepareGroupCreate(params);
    // prepared.actionSignatures has two entries — send both
    ```
  </Tab>
</Tabs>

참가자 키와 action signature의 본문 매핑(`message_id`, `encoded_message_event_detail`, 중첩된 `message_event_signature`)은 [시작하기 — 대화 키](/xchat/getting-started#4-set-up-conversation-keys)의 키 POST와 동일합니다.

멤버십이 변경될 때, 새 멤버 ID와 함께 현재 로스터(멤버, 관리자, 대기 중인 멤버, 설정된 경우 현재 제목/아바타/TTL)를 사용하여 \*\*`prepare_group_members_change`\*\*를 호출합니다. 대화 키를 교체하고, 그룹 생성처럼 **두 개**의 action signature를 반환합니다 — 모두 **멤버 추가**(`POST /2/chat/conversations/{id}/members`)에 POST하세요. 그런 다음 **키 변경** 트래픽이 예상됩니다: 이를 [시작하기의 키 교체](/xchat/getting-started#6-receive-and-decrypt)처럼 처리하세요(`extract_conversation_keys` / `decrypt_events`, 그런 다음 최신 버전으로 암호화).

`prepare_group_members_change`는 전달한 로스터에게만 래핑된 **새로운** 대화 키를 생성하므로, 새 멤버는 새 키 버전을 받고 이전 버전으로 전송된 메시지를 복호화할 수 없습니다. 반대로는 성립하지 않습니다: 교체는 **이전** 버전에 대한 접근을 결코 취소하지 않습니다 — 이미 이전 키를 보유한 사람은 누구나 그 키로 암호화된 메시지를 계속 읽을 수 있습니다. 대화 키가 노출되었다고 의심되면 `prepare_conversation_key_change`로 교체하세요. 이는 향후 메시지만 보호합니다.

***

## 암호화된 그룹 메타데이터

일부 대화 필드(예: 표시 **이름** 또는 **아바타 URL**)는 대화 키 아래 **암호화된** 상태로 도착할 수 있습니다. 이것은 `encrypt_message`가 **아닙니다**; 일반 Chat XDK의 **`encrypt` / `decrypt`** 쌍입니다 (UTF-8 문자열 입력, base64 암호문 출력, **원시** 대화 키 사용).

특정 필드가 암호화되어 저장되는지 여부는 그것을 쓰는 클라이언트가 결정합니다: `prepare_group_create`는 제공한 그대로 제목에 서명하고 전송합니다(대화 키는 그 호출이 생성하기 전까지 존재하지 않으므로, 생성 시점의 제목은 그것으로 암호화될 수 없습니다). 필드가 암호문인 대화를 읽을 때, 필드가 쓰여진 시점에 활성화되어 있던 키 버전과 `decrypt`로 복호화하세요.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Decrypt a field from the conversation object (name may vary by API shape)
    group_name = chat.decrypt(conversation["group_name"], raw_conv_key)

    # Encrypt before update if your API accepts ciphertext metadata
    encrypted_name = chat.encrypt("Project team", raw_conv_key)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const groupName = chat.decrypt(conversation.groupName, rawConvKey);
    const encryptedName = chat.encrypt('Project team', rawConvKey);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    // conv_key: &XChatConversationKey from extract_conversation_keys / decrypt_conversation_key
    let group_name = chat.decrypt(&conversation_group_name_b64, &conv_key)?;
    let encrypted_name = chat.encrypt("Project team", &conv_key)?;
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    groupName, err := chat.Decrypt(conversationGroupNameB64, rawConvKey)
    encryptedName, err := chat.Encrypt("Project team", rawConvKey)
    _ = groupName
    _ = encryptedName
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    string groupName = chat.Decrypt(conversationGroupNameB64, rawConvKey);
    string encryptedName = chat.Encrypt("Project team", rawConvKey);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    String groupName = chat.decrypt(conversationGroupNameB64, rawConvKey);
    String encryptedName = chat.encrypt("Project team", rawConvKey);
    ```
  </Tab>
</Tabs>

해당 메타데이터에 적용되는 **현재** 대화 키 버전을 사용하세요. 키가 교체되었다면, 필드가 쓰여진 시점에 활성화되어 있던 버전으로 복호화하세요(또는 메타데이터가 항상 교체 시 재작성되는 경우 제품 규칙을 따르세요).

***

## 메시지 및 이벤트

원시 대화 키를 가지고 있다면, 그룹에서 송수신은 1:1과 동일합니다:

* **전송:** `encrypt_message` → 메시지 전송 API ([시작하기](/xchat/getting-started#5-send-a-message))
* **수신:** 이벤트 API 또는 [실시간 전달](/xchat/real-time-events) → `decrypt_event` / `decrypt_events`
* **미디어:** 그룹 대화 ID와 함께 [미디어](/xchat/media)

멤버십 기반 교체 이후에는 항상 **최신** 키 버전으로 암호화하세요.

***

## 체크리스트

1. `POST /2/chat/conversations/group/initialize`로 g-접두사 ID를 발급합니다
2. **모든** 멤버로 `prepare_group_create`를 호출; 참가자 키 래핑과 **두 개 모두**의 action signature를 `POST /2/chat/conversations/group`에 POST
3. 원시 키 + 버전을 캐시; 키 변경 이벤트 시 업데이트
4. 멤버십 변경 시, `prepare_group_members_change`(두 개의 서명) → `POST /2/chat/conversations/{id}/members`
5. 필드가 암호문일 때 `decrypt`로 그룹 메타데이터를 복호화
6. 1:1과 동일한 패턴으로 송수신
