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 needs | A public HTTPS address, a certificate, a proxy rule | Outbound HTTPS to api.hijinks.team |
| Events arrive | At once | Within one poll interval (2 seconds in the reference bridge) |
| If the bridge is down | Each event is retried for 5 seconds, then dropped | Events wait up to 24 hours; stale ones are dropped on catch-up |
| Connect in the dashboard with | Your bridge's URL as the target URL | No 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
- 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.
- Start the reference bridge with
--poll(orBRIDGE_MODE=poll).BRIDGE_URLis 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. - Press Send test event. In poll mode it queues a
pingand 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
- Sign the query string exactly as sent (everything after
?) in place of a body. With no query string, sign the empty string:"<t>.". after: thenextcursor from your previous poll. Store it with the rest of the bridge's state, so a restart carries on where it stopped.after=latestanswers no events and the newest cursor: start from now. Use it on a first start, and whenever the saved cursor is lost. Leavingafterout 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
Code
- Verify
Hijinks-Signatureon 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
hasMoreis true, poll again at once. Otherwise wait for your interval. - Events are kept for 24 hours. When your
afteris 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 fromnext.
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 onemessage.idin a page, apply only the one with the highestsequence. - 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 sameafteragain and retry it until it goes through or expires; the events you already handled come back too, and you skip them byid. - 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.