Add Conversation Keys
Adds (initializes or rotates) the encryption keys for a Chat conversation. Call this before sending messages in a new 1:1 conversation, and again with a newer key version to rotate the conversation key.
For 1:1 conversations, provide the recipient’s user ID as the conversation_id. The server constructs the canonical conversation ID from the authenticated user and recipient.
The request body must contain the conversation key version and participant keys (the conversation key encrypted for each participant using their public key).
Workflow (new 1:1 conversation):
- Generate a conversation key using the SDK
- Encrypt the key for both participants using their public keys
- Call this endpoint to register the keys
- Send messages using
POST /chat/conversations/{id}/messages
To rotate the keys of an existing conversation, repeat the same call with a newer conversation key version.
Authentication:
- Requires OAuth 1.0a User Context or OAuth 2.0 User Context
- Required scopes:
tweet.read,users.read,dm.write
curl --request POST \
--url https://api.x.com/2/chat/conversations/{id}/keys \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"conversation_key_version": "<string>",
"conversation_participant_keys": [
{
"encrypted_conversation_key": "<string>",
"public_key_version": "<string>",
"user_id": "<string>"
}
],
"action_signatures": [
{
"encoded_message_event_detail": "<string>",
"message_event_signature": {
"public_key_version": "<string>",
"signature": "<string>",
"signature_version": "<string>",
"message_signing_key_info_list": [
{
"member_id": "<string>",
"public_key_version": "<string>",
"signing_public_key": "<string>"
}
],
"signing_public_key": "<string>"
},
"message_id": "<string>",
"signature_payload": "<string>"
}
],
"base64_encoded_key_rotation": "<string>"
}
'import requests
url = "https://api.x.com/2/chat/conversations/{id}/keys"
payload = {
"conversation_key_version": "<string>",
"conversation_participant_keys": [
{
"encrypted_conversation_key": "<string>",
"public_key_version": "<string>",
"user_id": "<string>"
}
],
"action_signatures": [
{
"encoded_message_event_detail": "<string>",
"message_event_signature": {
"public_key_version": "<string>",
"signature": "<string>",
"signature_version": "<string>",
"message_signing_key_info_list": [
{
"member_id": "<string>",
"public_key_version": "<string>",
"signing_public_key": "<string>"
}
],
"signing_public_key": "<string>"
},
"message_id": "<string>",
"signature_payload": "<string>"
}
],
"base64_encoded_key_rotation": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
conversation_key_version: '<string>',
conversation_participant_keys: [
{
encrypted_conversation_key: '<string>',
public_key_version: '<string>',
user_id: '<string>'
}
],
action_signatures: [
{
encoded_message_event_detail: '<string>',
message_event_signature: {
public_key_version: '<string>',
signature: '<string>',
signature_version: '<string>',
message_signing_key_info_list: [
{
member_id: '<string>',
public_key_version: '<string>',
signing_public_key: '<string>'
}
],
signing_public_key: '<string>'
},
message_id: '<string>',
signature_payload: '<string>'
}
],
base64_encoded_key_rotation: '<string>'
})
};
fetch('https://api.x.com/2/chat/conversations/{id}/keys', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.x.com/2/chat/conversations/{id}/keys",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'conversation_key_version' => '<string>',
'conversation_participant_keys' => [
[
'encrypted_conversation_key' => '<string>',
'public_key_version' => '<string>',
'user_id' => '<string>'
]
],
'action_signatures' => [
[
'encoded_message_event_detail' => '<string>',
'message_event_signature' => [
'public_key_version' => '<string>',
'signature' => '<string>',
'signature_version' => '<string>',
'message_signing_key_info_list' => [
[
'member_id' => '<string>',
'public_key_version' => '<string>',
'signing_public_key' => '<string>'
]
],
'signing_public_key' => '<string>'
],
'message_id' => '<string>',
'signature_payload' => '<string>'
]
],
'base64_encoded_key_rotation' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.x.com/2/chat/conversations/{id}/keys"
payload := strings.NewReader("{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.x.com/2/chat/conversations/{id}/keys")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.x.com/2/chat/conversations/{id}/keys")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"conversation_id": "<string>",
"sequence_id": "<string>"
},
"errors": [
{
"title": "<string>",
"type": "<string>",
"detail": "<string>",
"status": 123
}
]
}{
"code": 123,
"message": "<string>"
}Authorizations
The access token received from the authorization server in the OAuth 2.0 flow.
Path Parameters
The recipient's user ID for a 1:1 conversation, the hyphen-separated 1:1 conversation ID (e.g. '123-456'), or a group conversation ID (prefixed with 'g'). Identifies the conversation target. Accepts three formats: (1) a recipient user ID for 1:1 conversations (e.g., '1215441834412953600'), (2) a legacy 1:1 conversation ID with two user IDs separated by a dash (e.g., '1215441834412953600-1603419180975409153'), or (3) a group conversation ID prefixed with 'g' (e.g., 'g1234567890123456789'). The server constructs the canonical conversation ID from the authenticated user and recipient when a single user ID is provided.
^([0-9]{1,19}|[0-9]{1,19}-[0-9]{1,19}|g[0-9]{1,19})$"1215441834412953600"
Body
Version of the conversation encryption key (typically a timestamp in milliseconds).
The conversation key encrypted for each participant using their public key.
Show child attributes
Show child attributes
Cryptographic signatures for the key initialization action.
Show child attributes
Show child attributes
Base64-encoded key rotation payload for ratchet tree key management.
Response
The request has succeeded.
Show child attributes
Show child attributes
1An HTTP Problem Details object, as defined in IETF RFC 7807 (https://tools.ietf.org/html/rfc7807).
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
- Option 7
- Option 8
- Option 9
- Option 10
- Option 11
- Option 12
- Option 13
- Option 14
- Option 15
- Option 16
- Option 17
- Option 18
Show child attributes
Show child attributes
curl --request POST \
--url https://api.x.com/2/chat/conversations/{id}/keys \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"conversation_key_version": "<string>",
"conversation_participant_keys": [
{
"encrypted_conversation_key": "<string>",
"public_key_version": "<string>",
"user_id": "<string>"
}
],
"action_signatures": [
{
"encoded_message_event_detail": "<string>",
"message_event_signature": {
"public_key_version": "<string>",
"signature": "<string>",
"signature_version": "<string>",
"message_signing_key_info_list": [
{
"member_id": "<string>",
"public_key_version": "<string>",
"signing_public_key": "<string>"
}
],
"signing_public_key": "<string>"
},
"message_id": "<string>",
"signature_payload": "<string>"
}
],
"base64_encoded_key_rotation": "<string>"
}
'import requests
url = "https://api.x.com/2/chat/conversations/{id}/keys"
payload = {
"conversation_key_version": "<string>",
"conversation_participant_keys": [
{
"encrypted_conversation_key": "<string>",
"public_key_version": "<string>",
"user_id": "<string>"
}
],
"action_signatures": [
{
"encoded_message_event_detail": "<string>",
"message_event_signature": {
"public_key_version": "<string>",
"signature": "<string>",
"signature_version": "<string>",
"message_signing_key_info_list": [
{
"member_id": "<string>",
"public_key_version": "<string>",
"signing_public_key": "<string>"
}
],
"signing_public_key": "<string>"
},
"message_id": "<string>",
"signature_payload": "<string>"
}
],
"base64_encoded_key_rotation": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
conversation_key_version: '<string>',
conversation_participant_keys: [
{
encrypted_conversation_key: '<string>',
public_key_version: '<string>',
user_id: '<string>'
}
],
action_signatures: [
{
encoded_message_event_detail: '<string>',
message_event_signature: {
public_key_version: '<string>',
signature: '<string>',
signature_version: '<string>',
message_signing_key_info_list: [
{
member_id: '<string>',
public_key_version: '<string>',
signing_public_key: '<string>'
}
],
signing_public_key: '<string>'
},
message_id: '<string>',
signature_payload: '<string>'
}
],
base64_encoded_key_rotation: '<string>'
})
};
fetch('https://api.x.com/2/chat/conversations/{id}/keys', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.x.com/2/chat/conversations/{id}/keys",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'conversation_key_version' => '<string>',
'conversation_participant_keys' => [
[
'encrypted_conversation_key' => '<string>',
'public_key_version' => '<string>',
'user_id' => '<string>'
]
],
'action_signatures' => [
[
'encoded_message_event_detail' => '<string>',
'message_event_signature' => [
'public_key_version' => '<string>',
'signature' => '<string>',
'signature_version' => '<string>',
'message_signing_key_info_list' => [
[
'member_id' => '<string>',
'public_key_version' => '<string>',
'signing_public_key' => '<string>'
]
],
'signing_public_key' => '<string>'
],
'message_id' => '<string>',
'signature_payload' => '<string>'
]
],
'base64_encoded_key_rotation' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.x.com/2/chat/conversations/{id}/keys"
payload := strings.NewReader("{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.x.com/2/chat/conversations/{id}/keys")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.x.com/2/chat/conversations/{id}/keys")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"conversation_key_version\": \"<string>\",\n \"conversation_participant_keys\": [\n {\n \"encrypted_conversation_key\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"user_id\": \"<string>\"\n }\n ],\n \"action_signatures\": [\n {\n \"encoded_message_event_detail\": \"<string>\",\n \"message_event_signature\": {\n \"public_key_version\": \"<string>\",\n \"signature\": \"<string>\",\n \"signature_version\": \"<string>\",\n \"message_signing_key_info_list\": [\n {\n \"member_id\": \"<string>\",\n \"public_key_version\": \"<string>\",\n \"signing_public_key\": \"<string>\"\n }\n ],\n \"signing_public_key\": \"<string>\"\n },\n \"message_id\": \"<string>\",\n \"signature_payload\": \"<string>\"\n }\n ],\n \"base64_encoded_key_rotation\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"conversation_id": "<string>",
"sequence_id": "<string>"
},
"errors": [
{
"title": "<string>",
"type": "<string>",
"detail": "<string>",
"status": 123
}
]
}{
"code": 123,
"message": "<string>"
}