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.

GET /api/dice?clientSeed=guild_123_user_456_roll_2026_05_25&notation=4d6Example record
{
  "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
  }
}
  1. 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
  2. The roll is a pure function of both seedsHMAC-SHA256(serverSeed, "guild_123_user_456_roll_2026_05_25:0:0") → 4d6 = [2, 1, 3, 6], total 12
  3. After the roll, the seed is revealed and anyone can replay itserverSeed = 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&notation=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

Why this is fair

Where it fits

Related