What it is
A drop-in RNG layer for Discord bots. Replace the Math.random() that backs your /roll, /giveaway, /pick, and /raidloot slash commands with HTTP calls to /api/dice, /api/pick, /api/ints, and /api/shuffle. Every reply embeds the shortId permalink so any user can verify the roll without trusting the bot operator.
A roll and its proof
This is what anyone can check. The three values in the steps are all a verifier needs, and the replay button recomputes the roll right here, without calling Provable.
{
"clientSeed": "guild_123_user_456_roll_2026_05_25",
"nonce": 0,
"serverHash": "9a5467a93d1dc15d58c30bc3567d2d879e6da667a78339ec069340e4264ddbf4",
"outcome": {
"notation": "4d6",
"rolls": [2, 1, 3, 6],
"modifier": 0,
"total": 12
}
}- Before the roll, the server commits to a seedPublish this hash before the roll, so the seed can't be swapped afterwards.
serverHash = sha256(serverSeed) = 9a5467a93d1dc15d58c30bc3567d2d879e6da667a78339ec069340e4264ddbf4 - The roll is a pure function of both seeds
HMAC-SHA256(serverSeed, "guild_123_user_456_roll_2026_05_25:0:0") → 4d6 = [2, 1, 3, 6], total 12 - After the roll, the seed is revealed and anyone can replay it
serverSeed = 8d8fad15e767820eaeb287bf02d165834e00e11cb1a29311eabdd26490dd02a3
Try it live — a slash-command dice roll
The same call your /roll 4d6 handler will make:
curl "https://api.provable.io/api/dice?clientSeed=guild_123_user_456_roll_2026_05_25¬ation=4d6"
A /pick slash command — pick one option from a poll choices list:
curl "https://api.provable.io/api/pick?clientSeed=guild_123_poll_42&items=pizza,tacos,sushi,ramen,burgers"
Integration snippet
// discord.js v14 — /roll <notation> handler
import { SlashCommandBuilder } from "discord.js";
export const data = new SlashCommandBuilder()
.setName("roll")
.setDescription("Roll dice (NdM notation)")
.addStringOption((o) => o.setName("notation").setDescription("e.g. 3d6, 2d20+5").setRequired(true));
export async function execute(interaction) {
const notation = interaction.options.getString("notation");
// One seed per (guild, channel, user, interaction) — never reused.
const clientSeed = `g${interaction.guildId}_c${interaction.channelId}` +
`_u${interaction.user.id}_i${interaction.id}`;
const url = new URL("https://api.provable.io/api/dice");
url.searchParams.set("clientSeed", clientSeed);
url.searchParams.set("notation", notation);
const res = await fetch(url, {
headers: { "x-api-key": process.env.PROVABLE_KEY }
});
const { outcome, shortId } = await res.json();
await interaction.reply({
content: `🎲 \`${notation}\` → **${outcome.total}** ` +
`(${outcome.rolls.join(", ")})\n` +
`Verify: https://provable.io/o/${shortId}`,
});
}
Where this drops in
/roll— TTRPG dice via/api/dice./pick— random user, channel, or option via/api/pick./giveaway— winners from reaction lists via/api/pick(link the official rules to a pre-publishedserverHash)./raidloot— weighted drops from boss tables via/api/pickwith weights./coinflip— heads or tails via/api/intswithmin=1&max=2./randomuser— pick an active member for spotlights via/api/pick.
Why this is fair
- Per-interaction seeds. Building the
clientSeedfrom(guildId, channelId, userId, interactionId)guarantees seeds are never reused and the mapping is reproducible from public IDs. - Public permalinks. Embedding
https://provable.io/o/<shortId>in the bot reply turns "is your bot rigged?" into a closed conversation. - One key, one quota. The bot uses one Provable.io API key — the dashboard shows per-day rolls, top channels, and which command types dominate traffic.
Where it fits
- TTRPG communities running Pathfinder, D&D, indie systems.
- Esports / FGC servers doing fair seeding for tournament brackets.
- Streamer mod packs with giveaway / shout-out commands.
- Web3 community Discords running mint-day raffles or whitelist draws.