Snowflakes
GameVox returns IDs in Discord's 64-bit snowflake format wherever the Discord API would. The bit layout, sort order, and timestamp-extraction math are identical — your existing snowflake-parsing code works without changes.
Format
Identical to Discord's: 42-bit timestamp + 5-bit worker + 5-bit process + 12-bit increment, big-endian inside a 64-bit unsigned integer.
┌─ 42 bits ─────────────────┬─ 5 ─┬─ 5 ─┬─ 12 ────────┐
│ ms since GameVox epoch │ wkr │ prc │ increment │
└───────────────────────────┴─────┴─────┴─────────────┘
63 22 21 17 16 12 11 0 Each segment matches Discord:
- Timestamp (42 bits) — milliseconds since the GameVox epoch, see below.
- Worker ID (5 bits) — assigned per running api task at startup via Redis atomic counter.
- Process ID (5 bits) — paired with worker for a unique mint pool.
- Increment (12 bits) — sequence per millisecond per (worker, process).
Epoch
GameVox uses the same epoch as Discord:
1420070400000 (2015-01-01 00:00:00 UTC). A snowflake
parser written for Discord works on GameVox IDs unmodified — no
constant to swap.
const SNOWFLAKE_EPOCH = 1420070400000n; Wire format
Every snowflake on the REST / gateway wire is serialized as a
JSON string, not a number. JavaScript loses
precision above Number.MAX_SAFE_INTEGER
(253-1) and a snowflake regularly exceeds
that. Always parse via BigInt when you need to do
math, and store as a string everywhere else.
// Correct
const id = BigInt(message.id);
// Wrong — silent precision loss
const id = Number(message.id); Extracting the timestamp
JavaScript
function snowflakeToDate(snowflake) {
const EPOCH = 1420070400000n; // same as Discord
const ms = Number((BigInt(snowflake) >> 22n) + EPOCH);
return new Date(ms);
}
console.log(snowflakeToDate('182955831900192769'));
// → Date object — when this snowflake was minted Python
from datetime import datetime, timezone
SNOWFLAKE_EPOCH = 1420070400000
def snowflake_to_dt(snowflake):
ms = (int(snowflake) >> 22) + SNOWFLAKE_EPOCH
return datetime.fromtimestamp(ms / 1000, tz=timezone.utc) Java
long SNOWFLAKE_EPOCH = 1420070400000L;
long ms = (Long.parseUnsignedLong(snowflake) >>> 22) + SNOWFLAKE_EPOCH;
Instant when = Instant.ofEpochMilli(ms); Sort order
Because the timestamp is in the high bits, a lexicographic string
comparison of two snowflakes orders them chronologically — just
like Discord. This is why before / after
/ around pagination on the messages endpoint works
without an explicit timestamp field.
// In a message array, oldest → newest:
messages.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0); Native data and the alias table
Under the hood, GameVox-native objects (servers, channels, users,
messages, etc.) use UUIDv4. The bot-compatibility
layer mints a stable snowflake the first time an object is exposed
to a bot and persists the pairing in the
snowflake_aliases table. Every subsequent reference to
that object — over REST or gateway — uses the same snowflake.
For messages we backfill snowflakes with the message's
created_at as the source timestamp, so historical
messages sort correctly when a bot first joins a channel. For
everything else the alias is minted lazily at first exposure with
NOW() as the timestamp, so the snowflake reflects when
the bot saw the object, not necessarily when it was created.
Sharding hash
The standard Discord shard formula works as-is:
shardId = (BigInt(guildId) >> 22n) % BigInt(numShards);
Bots under 2,500 servers run on a single shard ([0, 1])
and never need to think about this. Larger bots get the same
even-ish hash distribution Discord provides because the top 42 bits
are time-monotonic, not random.
Gotchas
- Don't compare snowflakes with
==against a number literal. They're strings on the wire — compare to a string or convert both toBigInt. - The 42-bit timestamp wraps after roughly 139 years past the epoch. GameVox's snowflakes are good through 2165.
- Don't infer creation time for a server / channel /
role from its snowflake. Native objects predating the
bot platform got their snowflake at first bot exposure, not at
creation. Messages are the exception (backfilled with
created_at).