Private beta: this contract may change before launch. Join the waitlist
Hijinks
Guides

Events

The API reference holds every field and an example of each event; this page is the working overview.

Events Hijinks sends

POST to your bridge URL with Content-Type: application/json and three headers: Hijinks-Signature, Hijinks-Event-Id and Hijinks-Workspace. Answer any 2xx within 5 seconds.

typeWhenFieldsRetries
message.createPost a message in a channelchannel, message+1 s, +4 s, then dropped
message.updateReplace a message Hijinks created (each question, answer counts, reveal, final board)channel, message (same message.id)a question, reveal or final board: +1 s, +4 s, then dropped; anything else never — the next update supersedes it
message.ephemeralA reply only the member who acted should seechannel, user, message+1 s, +4 s, then dropped
dm.createA private message to a member (async rounds, Know Your Team, reminders)user, message+1 s, +4 s, then dropped
ping"Send test event" in the dashboard—never

Every event also carries id, type, workspace, sequence, createdAt and expiresAt (when it goes stale; see Retries and ordering). A question looks like this: an update of the message the game started with.

JSONCode
{ "id": "evt_01JA2Z8KQ4M6V3XG5H7N9PRSTW", "type": "message.update", "workspace": "6f0c2b9e-8d4a-4f6b-9c1e-2a7d5e0b4c83", "sequence": 1760018640000001, "createdAt": "2026-10-09T14:04:00.000Z", "expiresAt": "2026-10-09T14:14:00.000Z", "channel": "town-square", "message": { "id": "msg_01JA2Z8KQ4M6V3XG5H7N9PRSTW", "kind": "question", "text": "*🧠 Question 3 of 5 · Film · Medium*\n**Which film was marketed with …?**\n\n*⏱ 30 seconds to answer*", "buttons": [ [{ "label": "Alien", "action": "answer_2_0" }], [{ "label": "Gravity", "action": "answer_2_1" }] ], "image": null, "awaitsReply": false, "data": { "questionIndex": 2, "totalQuestions": 5, "category": "Film" } } }

Rendering a message

  • text is chat-flavoured Markdown, ready to print: *italic*, **bold**, code, links, quotes. A bare \n is a line break, as in most chat tools. If your chat renders strict CommonMark, turn on hard line breaks (markdown-it's breaks: true, for example), or lines will run together.
  • buttons are rows, top to bottom. A button is either { label, action } — send action back when it is pressed — or { label, url }, a link to open (the Pictionary drawing room, for one).
  • image is a picture or GIF URL to show with the message, or null.
  • awaitsReply means Hijinks would like the member's next typed message to answer this one; offer a reply box if your chat has one. In a DM no reply is needed: the member's next message there answers it.
  • kind names the message (question, time_up, game_end, leaderboard, wordle_board and 27 more) so a bridge can special-case a few and print the rest. data carries the view model behind it, for bridges that draw their own UI.
  • A message.update for a message first sent as a DM arrives with channel: "dm:<user id>". Send button presses and typed messages from a member's DM back the same way, as action and message events with channel: "dm:<their own user.id>": replies then go to the DM, and a typed message there answers the Know Your Team question (or any other prompt) waiting in it. Any other channel id starting with dm: is refused.

Events your bridge sends

POST https://api.hijinks.team/hooks/<workspace id>, signed the same way. user is { "id", "name" } (ids up to 128 characters; send the name whenever you have it, it is what leaderboards print). channel is an id, or { "id", "name" }. Unknown fields are refused, so a typo fails loudly. Every event may also carry your own id for the request (up to 128 characters, unique per request): send it, and retries become safe.

typeMeaningFields
commandA member ran /hijinks <text>channel, user, text
actionA member pressed a buttonchannel, user, messageId, action
messageA member typed something (word games, rebus, charades, replies)channel, user, text, replyTo?

A typed message that answers nothing in play is accepted and dropped, so a bridge can forward every message in a channel without filtering. All visible output arrives as outbound events, never in the HTTP response.

StatusMeaning
202Accepted: {"ok":true}. What it causes arrives as outbound events.
400Malformed JSON, or the body does not match the schema; field names the culprit.
401Missing or wrong Hijinks-Signature.
404Unknown, non-webhook or disconnected workspace.
409Signature valid, but the timestamp is more than 5 minutes off (replay or clock drift).
413Body larger than 64 KB.
429More than 500 requests a second from your IP address (checked first, signed or not), or more than 250 signed requests a second for this workspace. Wait Retry-After seconds and send it again.

Rate limits and retries

Each button press and typed answer is one request, so the limit is set for a whole game answering in the same second: 250 signed requests a second per workspace covers 200 players, the largest per-game cap of any plan that has one, with room for polls. Bridges for several workspaces behind one egress address share that address's 500 a second.

Hijinks acts on a request once. A request repeating an id sent in the last 24 hours is answered 202 and ignored; without an id, only an exact replay (same signature, same bytes) is. So:

  • Retry a timeout, a dropped connection, a 5xx or a 429 (after Retry-After), with the same body and the same id, re-signed with the current time.
  • Don't retry any other 4xx: the request itself is wrong, and field says where.

Without an id, a retry re-signed with a new timestamp counts as a new request: a word-game guess sent twice costs two turns.

Retries and ordering

  • Every delivery has a 5-second deadline. Creates, DMs, ephemeral replies and the updates that start a phase of a game (a question, a reveal, the final board) are retried after 1 and 4 seconds, then dropped — a question that arrives 20 seconds late is worse than none. Other updates (answer counts, tallies) and pings are never retried: the next update supersedes them.
  • A retry carries the same event id. Dedupe on Hijinks-Event-Id, and answer 2xx before doing slow work.
  • sequence increases per workspace, so events that arrive out of order can be put back in order: skip an update older than one you have applied.
  • expiresAt says when an event stops being worth showing: 60 seconds for ephemeral replies and pings, 10 minutes for a live game's messages and command answers, null (never) for self-paced rounds, word games, icebreakers and digests. Drop an event past it, unapplied.
  • The game runs on its own clock and never stalls a round for a bridge that is down. A push bridge that is down misses what is sent meanwhile. A polling bridge finds it all waiting for 24 hours, and drops what expired on the way (Catching up).
  • Your bridge URL must be public HTTPS. Hijinks does not follow redirects and will not deliver to private or loopback addresses.
Last modified on