Every RPC request, response, audio frame, and event uses the same envelope.
Frame layout
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
| length (u32 LE) |
+---------------------------------------------------------------+
| method_id (u32 LE) |
+---------------------------------------------------------------+
| |
. serde envelope body (length-4 bytes) .
| |
+---------------------------------------------------------------+
| Field | Width | Meaning |
|---|
length | 4 bytes | Little-endian byte count of method_id + body. Excludes itself. Wire total = 4 + length. |
method_id | 4 bytes | Little-endian. One of the Method IDs. |
body | length - 4 | A serde envelope (see below). |
length does not include the four bytes of the length field itself. A
zero-arg request (Empty) has length = 4 + sizeof(serde-envelope-header),
not zero.
Serde envelope
The body of every frame is a serde envelope:
+----+----+--------------------+-----------------------+
| u8 | u8 | i32 LE | fields... |
+----+----+--------------------+-----------------------+
v cv payload_size (payload_size B)
| Field | Width | Meaning |
|---|
version | 1 byte | Schema version of the producer. |
compat_version | 1 byte | Oldest version the producer knows it is compatible with. |
payload_size | 4 bytes (signed LE) | Byte count of the fields that follow. Used to skip past unknown trailing fields. |
fields | variable | Each field encoded inline in declaration order. |
A receiver that doesn’t recognize a tail of newer fields simply skips
(payload_size - bytes_consumed) bytes and moves on. This is how forward
compatibility works.
Field encoding
Primitives are little-endian, two’s-complement, no padding:
| Logical type | Wire encoding |
|---|
bool | u8 (0 or 1) |
int32 / uint32 | 4 bytes LE |
int64 / uint64 | 8 bytes LE |
double | 8 bytes IEEE 754 LE |
enum | encoded as int32 LE |
string | i32 LE length followed by length bytes of UTF-8 |
vector<T> | i32 LE length followed by length elements |
| nested struct | another full serde envelope (recursive) |
Strings are length-prefixed, not null-terminated. Binary payloads (e.g.
µ-law audio) are also encoded with the string shape — the bytes are
treated as opaque.
Worked example: BargeRequest
Schema:
struct BargeRequest {
string call_sid;
};
Serialize call_sid = "abc" with method_id = 3854301714 (Barge):
length = 14 (4 method_id + 10 envelope body)
method_id = 3854301714
envelope:
version = 0
compat_ver = 0
payload_sz = 7 (4 length-prefix + 3 string bytes)
call_sid = "abc" → 03 00 00 00 61 62 63
On the wire (hex, spaces for clarity):
0e 00 00 00 length = 14
12 64 b0 e5 method_id = 3854301714
00 version
00 compat_version
07 00 00 00 payload_size = 7
03 00 00 00 string length = 3
61 62 63 "abc"
Total bytes written: 18 (4 + length).
Reading a frame
import struct
def read_frame(stream):
header = stream.recv_exact(4)
length = struct.unpack("<I", header)[0]
body = stream.recv_exact(length)
method_id = struct.unpack("<I", body[:4])[0]
payload = body[4:] # serde envelope
return method_id, payload
The serde envelope can then be parsed structurally per the schema for that
method_id.