Webhooks

Polyhedral can send real-time HTTP POST requests to your server when events happen in your games — chat messages, dice rolls, notes, encounters, and more.

Quick Start

  1. A paid plan is required. Webhooks are not available on the free plan.
  2. Open Connected Apps from your avatar menu and switch to the Webhooks tab.
  3. Select a game, click Create Webhook, and enter your HTTPS endpoint URL.
  4. Choose which events to subscribe to.
  5. Copy the signing secret — you won't see it again. Use it to verify that deliveries come from Polyhedral.
  6. Use the Test button to send a sample event to your endpoint.

Payload Format

Every delivery is a JSON POST with three top-level fields:

{
  "event": "note.created",
  "timestamp": "2026-01-30T12:00:00.000Z",
  "data": {
    "note_id": "abc-123",
    "title": "Session Log"
  }
}

Headers

Every delivery includes these custom headers:

Events

Subscribe to any combination of events when creating or editing a webhook.

Chat

chat.message.created

A new chat message was sent (user or AI).

{ "message_id": "uuid", "role": "user" | "assistant" }

Dice

dice.rolled

Dice were rolled.

{ "formula": "2d6+3", "reason": "Attack roll", "result": 11 }

Notes

note.created

A note was created.

{ "note_id": "uuid", "title": "Session Log" }
note.updated

A note was updated.

{ "note_id": "uuid", "title": "Session Log" }

Encounters

encounter.created

An encounter was created.

{ "encounter_id": "uuid", "name": "Goblin Ambush" }
encounter.updated

An encounter was updated.

{ "encounter_id": "uuid", "name": "Goblin Ambush" }

Members

game.member.joined

A new member joined the game.

{ "member_id": "uuid", "user_id": "uuid", "display_name": "Player" }

Resources

resource.added_to_game

A resource was added to the game library.

{ "resource_id": "uuid", "game_resource_id": "uuid" }

Rollable Tables

table.created

A rollable table was created.

{ "table_id": "uuid", "name": "Random Encounters" }
table.rolled

A rollable table was rolled on.

{ "table_id": "uuid", "name": "Random Encounters", "result": "Owlbear" }
table.updated

A rollable table was updated.

{ "table_id": "uuid", "name": "Random Encounters" }

Verifying Signatures

Use your webhook signing secret to verify that deliveries actually come from Polyhedral. Compute the HMAC-SHA256 of the raw request body and compare it to the X-Polyhedral-Signature header.

Node.js

import crypto from 'crypto';

function verifySignature(payload, secret, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  return signature === `sha256=${expected}`;
}

// In your handler:
const sig = req.headers['x-polyhedral-signature'];
const body = JSON.stringify(req.body);
if (!verifySignature(body, process.env.WEBHOOK_SECRET, sig)) {
  return res.status(401).send('Invalid signature');
}

Python

import hmac, hashlib

def verify_signature(payload: bytes, secret: str, signature: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, f"sha256={expected}")

# In your handler:
sig = request.headers.get("X-Polyhedral-Signature", "")
if not verify_signature(request.data, WEBHOOK_SECRET, sig):
    abort(401)

Retry Logic

If your endpoint returns a 5xx error or times out (10 seconds), Polyhedral will retry the delivery up to 5 more times with increasing delays:

  1. Immediate
  2. After 1 minute
  3. After 10 minutes
  4. After 1 hour
  5. After 6 hours

4xx responses are not retried — they indicate a client-side problem that retrying won't fix.

Manage your webhooks in Connected Apps from your avatar menu. For programmatic access, see the MCP documentation.