Brook Shiliew Cheap Zhi Meeting Nan mitmproxy client Network Activity Monitor iOS Old App Downloader SSH Terminal Subtitle 菜根谭 Socks5 Configurator IPvBar File Link Markdown Editor IP AI nami joker brook ipio nico z zoro hancock mad httpserver google hosts

Zhi Upstream API Documentation

Updated at: 2025-02-21

You should read Are WhatsApp, Signal, Telegram really end-to-end encrypted? first.

This Documentation describes all data for upstream API requests.

GET SigninSendCode

  • Email address

If you don't want to use email, you can obtain an anonymous account with BTC.

GET Signin

  • Email address
  • The verification code
  • Upload back the Code Token which responsed by SigninSendCode

GET Auth

  • auth token

GET CreateChat

  • auth token

GET JoinChat

  • auth token
  • Chat UUID

GET GetChat

  • auth token
  • Chat UUID

GET LeaveChat

  • auth token
  • Chat UUID

POST LeaveOtherChats

  • auth token
  • Chat UUID list

GET GetChatUUIDList

  • auth token

GET GetVIPChatToken

  • auth token

GET Time

  • auth token

POST LastPageMessages

  • auth token
  • Chat UUID list

GET PrevPageMessages

  • auth token
  • Chat UUID
  • Message UUID

GET FirstLastPageMessages

  • auth token
  • Chat UUID

GET ChatMembers

  • auth token
  • Chat UUID

GET IAP

  • auth token
  • Transaction ID generated by Apple when you subscribed on Apple IAP. No complicated, opaque things.

GET ws

  • auth token

  • Device UUID, because Zhi allow one account used on multiple devices, and Zhi will forward encrypted messages to all your devices.

  • Streamed encrypted message, the message is json format, like this:

    {
      "ChatUUID": "the Chat UUID",
      "UserUUID": "your UUID in this Chat",
      "MessageUUID": "the message UUID",
      "Payload": "the encrypted message"
    }
    

You can verify whether the Payload encrypted by your Chat Key and ChatUUID and your UserUUID, try to decrypt with this javascript function:

async function decrypt_payload(Key, ChatUUID, UserUUID, Payload) {
    var b = Uint8Array.from(Payload.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
    var k = await crypto.subtle.deriveKey(
        { name: 'HKDF', hash: 'SHA-256', salt: b.subarray(0, 12), info: new TextEncoder().encode(ChatUUID + UserUUID) },
        await crypto.subtle.importKey('raw', new TextEncoder().encode(Key), 'HKDF', false, ['deriveKey']),
        { name: 'AES-GCM', length: 256 },
        false,
        ['decrypt']
    );
    var ab = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: b.subarray(0, 12) },
        k,
        b.subarray(12)
    );
    return new TextDecoder().decode(ab)
}

GET UpdatePushToken

As we know, as an instant messaging app, we need to implement push notifications.

  1. On the Apple platform: We must choose Apple APN (Apple Push Notification service).
  2. On the Android platform: We almost have no choice but to use Google FCM (Firebase Cloud Messaging).
  3. On the Web platform: Currently, we are only developing and doing compatibility testing on Chrome. To implement push notifications, we must use Google GCM (Google Cloud Messaging). Accessing: chrome://gcm-internals/

Because Zhi server cannot decrypt messages, when there is a new message, we can only push a bland "New Message" string to the push service, which then sends it to the user's device. Therefore, neither Apple nor Google can decrypt your messages.

  • auth token
  • Device UUID, because Zhi allow one account used on multiple devices, and Zhi will push notification to all your devices.
  • Push Token which generated by Apple or Google FCM or Google GCM

GET room/ws

We use standard WebRTC technology to implement Meeting. WebRTC requires STUN stun.shiliew.com:3478 and TURN turn.shiliew.com:3478 server.

  1. We know that WebRTC is end-to-end encrypted, STUN and TURN will not break WebRTC's end-to-end encryption.
  2. And we enforce the use of TURN, so when you have a Meeting with other members, the other members are not able to know your IP.
  3. Because the key negotiation for WebRTC is achieved through signaling interaction. Therefore, it's essential to ensure the security of the signaling being transmitted.

Don't worry, all your signaling, just like the messages above, is also encrypted with your Chat Key before transmission.

  • auth token

  • Chat UUID

  • UUID for current WebRTC session

  • Streamed encrypted signaling, the signaling is json format, like this:

    {
      "Kind": 0, // A numerical value representing the steps of WebRTC signaling.
      "Member": "the Member UUID you want to send the signaling to",
      "Payload": "the encrypted signaling"
    }
    

You can verify whether the Payload encrypted by your Chat Key and ChatUUID and your UserUUID, try to decrypt with this javascript function:

async function decrypt_payload(Key, ChatUUID, UserUUID, Payload) {
    var b = Uint8Array.from(Payload.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
    var k = await crypto.subtle.deriveKey(
        { name: 'HKDF', hash: 'SHA-256', salt: b.subarray(0, 12), info: new TextEncoder().encode(ChatUUID + UserUUID) },
        await crypto.subtle.importKey('raw', new TextEncoder().encode(Key), 'HKDF', false, ['deriveKey']),
        { name: 'AES-GCM', length: 256 },
        false,
        ['decrypt']
    );
    var ab = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: b.subarray(0, 12) },
        k,
        b.subarray(12)
    );
    return new TextDecoder().decode(ab)
}

PUT upload.zhi.shiliew.com

We use a file server to store encrypted avatars, images, videos, and files in order to reduce the size of the message body.

Don't worry, all your avatars, images, videos, and files, just like the messages above, is also encrypted with your Chat Key before transmission.

  • auth token
  • Chat UUID
  • File type used for determining the use of different storage levels
  • Encrypted file

You can verify whether the Payload encrypted by your Chat Key and ChatUUID and your UserUUID, try to decrypt with this javascript function:

// Payload is the http body as Uint8Array, return the decrypted Uint8Array
async function decrypt_payload(Key, ChatUUID, UserUUID, Payload) {
    var decrypt = async function(Key, ChatUUID, UserUUID, b) {
        var k = await crypto.subtle.deriveKey(
            { name: 'HKDF', hash: 'SHA-256', salt: b.subarray(0, 12), info: new TextEncoder().encode(ChatUUID + UserUUID) },
            await crypto.subtle.importKey('raw', new TextEncoder().encode(Key), 'HKDF', false, ['deriveKey']),
            { name: 'AES-GCM', length: 256 },
            false,
            ['decrypt']
        );
        var ab = await crypto.subtle.decrypt(
            { name: 'AES-GCM', iv: b.subarray(0, 12) },
            k,
            b.subarray(12)
        );
        return new Uint8Array(ab)
    }
    var b = Payload
    var r = new Uint8Array()
    for (; true;) {
        if (b.length == 0) {
            break
        }
        var n = 524316
        if (b.length < 524316) {
            n = b.length
        }
        var b1 = await decrypt(Key, ChatUUID, UserUUID, b.slice(0, n))
        var r1 = new Uint8Array(r.length + b1.length);
        r1.set(r)
        r1.set(b1, r.length)
        r = r1
        b = b.subarray(n)
    }
    return r
}

GET zhi.shiliew.com

  • auth token
  • Chat UUID
  • File type used for determining from which storage tier to retrieve files.
  • File UUID

If you discover a security vulnerability, please send an e-mail to cloud@txthinking.com. Thank you very much.

© TxThinking, Inc. All Rights Reserved.