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

Polling instead of an inbound URL

By default Hijinks pushes each event to your bridge's public HTTPS address. If your chat server lives on an intranet with outbound access only, the bridge can poll instead: it asks Hijinks for new events every couple of seconds. Nothing on your network has to accept a connection from the internet.

Push (default)Poll
Your bridge needsA public HTTPS address, a certificate, a proxy ruleOutbound HTTPS to api.hijinks.team
Events arriveAt onceWithin one poll interval (2 seconds in the reference bridge)
If the bridge is downEach event is retried for 5 seconds, then droppedEvents wait up to 24 hours; stale ones are dropped on catch-up
Connect in the dashboard withYour bridge's URL as the target URLNo target URL

Both modes carry the same events: the same envelopes, ids and sequence numbers, the same signature. Everything on What works over a bridge works in either.

Set it up

  1. In the dashboard, connect the custom platform and choose Poll under "How events reach your bridge". The endpoint is then a polling one: Hijinks holds its events instead of sending them, and the dashboard shows the events URL next to the inbound URL. Setting a target URL later in the workspace settings switches it to push; clearing it switches back. Before you switch to push, let the bridge drain what is held with one last poll: from that moment polls are refused, and events not fetched by then are never pushed.
  2. Start the reference bridge with --poll (or BRIDGE_MODE=poll). BRIDGE_URL is the address Mattermost uses to reach the bridge, which may be internal. Keep its state file on a volume (the Docker image's /data): the cursor lives there. The README has an intranet section.
  3. Press Send test event. In poll mode it queues a ping and reports success when the bridge has polled within the last minute.

Firewall and proxy

The bridge only makes outbound requests: HTTPS (TCP 443) to api.hijinks.team. Ask for that one destination. If outbound traffic must go through a corporate proxy, set HTTPS_PROXY (and NO_PROXY for your chat server's address) and, on Node 24 — the reference bridge's Docker image — NODE_USE_ENV_PROXY=1, which makes Node's built-in fetch use them.

The request

Code
GET /hooks/<workspace id>/events?after=4182 HTTP/1.1 Host: api.hijinks.team Hijinks-Signature: t=1760018640,v1=<hex HMAC-SHA256(secret, "1760018640.after=4182")>
  • Sign the query string exactly as sent (everything after ?) in place of a body. With no query string, sign the empty string: "<t>.".
  • after: the next cursor from your previous poll. Store it with the rest of the bridge's state, so a restart carries on where it stopped.
  • after=latest answers no events and the newest cursor: start from now. Use it on a first start, and whenever the saved cursor is lost. Leaving after out instead starts from the oldest event held, which replays up to 24 hours of games into your channels.
  • limit: 1 to 100, default 100.

A cursor never skips an event: an event still being written when you poll is served after the cursor you got, never before it.

The response

JSONCode
{ "events": [ { "id": "evt_01JA2ZD9E0F1G2H3J4K5M6N7P8", "type": "ping", "workspace": "6f0c2b9e-…", "sequence": 1760018880000007, "createdAt": "2026-10-09T14:08:00.000Z" } ], "next": "4183", "hasMore": false, "gap": false }
  • Verify Hijinks-Signature on the response over its exact bytes, exactly as you would a pushed event (Verify signatures), before acting on anything in it.
  • Handle each event as if it had been pushed, oldest first. Dedupe on id: if your bridge stops between handling an event and saving the cursor, the next poll repeats it.
  • When hasMore is true, poll again at once. Otherwise wait for your interval.
  • Events are kept for 24 hours. When your after is older than that, the page says "gap": true: something expired before you fetched it. Post a note in the channel if you like ("Hijinks was unreachable; some messages were missed") and carry on from next.

Catching up after downtime

Events wait while your bridge is down, but most of them are worth nothing an hour later. Every event carries expiresAt:

  • Drop an event past its expiresAt, unapplied. Replies to one member and pings expire after 60 seconds; a live game's messages and the answers to commands after 10 minutes; self-paced rounds, word games with their own end, icebreakers and digests never (null).
  • Collapse updates: of several message.updates for one message.id in a page, apply only the one with the highest sequence.
  • An update for a message whose create you dropped is dropped with it, as usual.

The reference bridge does all three.

When your chat refuses a message

  • A create (message.create, dm.create) your chat refused: don't advance past it. Poll the same after again and retry it until it goes through or expires; the events you already handled come back too, and you skip them by id.
  • An update or an ephemeral reply: drop it. The next update supersedes it, and a reply is stale within a minute anyway.

If the bridge stops polling

The dashboard calls a polling bridge failing when events have waited for it for 5 minutes without a poll, and the workspace's owner gets one email per outage. The next poll clears it.

Errors

The checks and their order are those of POST /hooks/{workspaceId}: 429 for too many requests (polls count against the same limits), 404 for an unknown workspace, 401 for a bad signature over the query string, 409 for a timestamp more than 5 minutes off. Then 400 names a malformed, unknown or repeated query parameter, and 403 means the endpoint has a target URL, so its events are pushed and never held. The API reference has the full contract.

Last modified on