Documentation

Payload Formats

Encode text or JSON, validate received data, and adapt a serializer with no change to the SDK.

Before you start: obtain a connected chat segment with the setup from Messaging. Each format below is an alternative. Both the sender and the receiver must agree on the format.

1. Start with text

from useceleris_client import read_text, text_payload

stop_text = chat.on_message(lambda payload, metadata: print(read_text(payload)))
await chat.publish(text_payload("Hello"))
# Later, when this receiver is no longer needed:
stop_text()

Celeris carries opaque bytes. It does not infer a content type, compress JSON automatically, or translate formats for recipients. read_text raises ConfigurationError for bytes that are not valid UTF-8.

2. Send and validate JSON

read_json returns whatever the JSON holds. Validate anything received from a publisher that you do not control. One option is Pydantic, which the client already depends on:

from typing import Literal

from pydantic import BaseModel, ValidationError

from useceleris_client import ConfigurationError, MessageMetadata, json_payload, read_json


class Typing(BaseModel):
    type: Literal["typing"]
    active: bool


def receive(payload: bytes, metadata: MessageMetadata) -> None:
    try:
        event = Typing.model_validate(read_json(payload))
    except (ConfigurationError, ValidationError):
        print("Ignored an invalid typing event.")
        return

    print("Peer typing:", event.active)


stop_json = chat.on_message(receive)
await chat.publish(json_payload({"type": "typing", "active": True}))

json_payload writes compact JSON, as JavaScript's JSON.stringify does. Floats keep Python's spelling (1.0, 1e-07 where JavaScript writes 1, 1e-7). Compare decoded values across SDKs, not the bytes. json_payload raises ConfigurationError for values that JSON cannot represent: NaN, infinities, sets, bytes, other non-JSON types, and circular structures. read_json raises ConfigurationError for invalid UTF-8 or malformed JSON. Render received text safely. Do not insert it as HTML.

3. Bring your own serializer

The SDK includes one adapter, not a separate bundled library for every format. Your encoder must return bytes. Errors from your encoder or decoder propagate unchanged.

MessagePack

pip install msgpack
import msgpack

from useceleris_client import MessageMetadata, create_payload_codec

codec = create_payload_codec(
    encode=lambda value: msgpack.packb(value),
    decode=lambda payload: msgpack.unpackb(payload),
)


def receive(payload: bytes, metadata: MessageMetadata) -> None:
    reading = codec.read_payload(payload)
    print(reading)  # Validate the decoded value before trusting it.


stop_binary = chat.on_message(receive)
await chat.publish(codec.encode_payload({"sensor": "temperature", "value": 21.5}))

MessagePack keeps binary fields and distinguishes integers from floats, which JSON cannot.

Protobuf

This example uses classes that protoc generated from a schema such as this one:

reading.proto
syntax = "proto3";

package demo;

message Reading {
  string sensor = 1;
  double value = 2;
}
from reading_pb2 import Reading  # generated by protoc --python_out

from useceleris_client import create_payload_codec

readings = create_payload_codec(
    encode=lambda reading: reading.SerializeToString(),
    decode=Reading.FromString,
)

await chat.publish(readings.encode_payload(Reading(sensor="temperature", value=21.5)))

The numbers in the schema identify fields on the wire. Keep them stable when you change your schema. A Python receiver and a JavaScript sender interoperate when both use the same schema.

Mix text, JSON, MessagePack, and Protobuf receivers on one segment only when your application can distinguish the formats. Separate segments or an explicit envelope can provide that distinction. A successful decode does not prove who sent the message.

4. Handle timestamps

The SDK's metadata and your message payload are separate. Your serializer decides the types inside a payload. All of the SDK's own numeric fields are int values:

ValuePython type
Presence request page and per_pageint
Presence result total, per_page, current_page, from_, toint
Connection and presence-query timeouts, in millisecondsint
Credential request disconnected_at and replay_lookback_msint or None
Recovery event retry_indexint
Message, notice, presence-event, and presence-record timestampint, Unix ms

A timestamp converts to a datetime directly and serializes to JSON as a number with no loss:

import json
from datetime import datetime, timezone

from useceleris_client import MessageMetadata


def record(payload: bytes, metadata: MessageMetadata) -> None:
    sent_at = datetime.fromtimestamp(metadata.timestamp / 1000, tz=timezone.utc)
    print(sent_at.isoformat())
    print(json.dumps({"message_id": metadata.message_id, "timestamp": metadata.timestamp}))


stop_metadata = chat.on_message(record)

A JavaScript consumer that needs the exact value must read large timestamps from that JSON as bigint. Do not log sensitive payloads, identities, or error resources in production.

Next: Presence. For format tradeoffs, see Binary Messaging.

We use Google cookies for analytics and for advertising measurement, only if you allow them. Until you choose, Google sets no cookies. Refer to our Cookie Policy.