Webhooks
What this is
You give Arena54 an https address. When something changes, Arena54 sends it a signed POST with a small JSON body. You no longer have to ask on a timer.
Add an address
- In the dashboard, under Webhooks, enter the address and tick the events you want.
- Copy the signing secret (it starts
a54whsec_). It is shown once. Keep it on your server, for example asARENA54_WEBHOOK_SECRET. - Press Send a test event. A
pingarrives in the same shape as a real event, and the result shows in the log.
The address must be public https on the usual port, with a host name. An IP address, a local name or a redirect is refused.
Events
| Event | When | data |
|---|---|---|
| listing.published | A game, studio or person was approved and is now listed. | kind, slug or handle, name, url, api |
| listing.updated | A published listing was changed. | kind, slug or handle, name, url, api |
| tip.paid | A player tipped one of your registered games through POST /v1/play/tips. Sent only to that game’s maker. | kind, slug or handle, name, url, api |
| ranking.moved | A listing moved on the weekly board. Checked once a day, at 04:00 UTC. | kind, board, name, url, rank, previous_rank |
| ping | You pressed “Send a test event”. | message |
POST /your-address
x-arena54-event: listing.updated
x-arena54-delivery: d856e71f-2361-4fb9-8a53-8dc2b12f65ca
x-arena54-timestamp: 1791833661
x-arena54-signature: 5f1c…
{ "id": "d856e71f-2361-4fb9-8a53-8dc2b12f65ca", "event": "listing.updated", "created_at": "2026-10-12T19:34:21.387Z",
"data": { "kind": "game", "slug": "lagos-run", "name": "Lagos Run", "url": "https://arena54.com/g/lagos-run", "api": "/games/lagos-run" } }An event tells you that something changed and where to read it. Call data.api on the API for the listing as it is now. Only published listings are ever sent.
Verify a delivery
The signature is HMAC-SHA256, in hex, over the timestamp, a dot, and the body exactly as received. Check it before you trust anything, and refuse a timestamp more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
export function isFromArena54(rawBody, headers) {
const t = headers["x-arena54-timestamp"], sig = headers["x-arena54-signature"] ?? "";
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = createHmac("sha256", process.env.ARENA54_WEBHOOK_SECRET).update(t + "." + rawBody).digest("hex");
return want.length === sig.length && timingSafeEqual(Buffer.from(want), Buffer.from(sig));
}- Use the raw body, before any JSON parsing.
- Answer with any 2xx within 8 seconds. Do slow work after you answer.
- A retry carries the same
idandx-arena54-delivery. If you have handled that id, answer 200 and do nothing.
Retries and the log
Anything other than a 2xx is a failure. A failed delivery is tried up to six times in all, with waits that grow from one minute to twelve hours. Retries go out when the next event is sent and once a day (04:00 UTC), so a quiet day can stretch a wait. The dashboard lists each address’s recent deliveries with the answer it gave, and Send again tries one now.
Plans
Any plan can add an address and send it test events. Real events are sent to accounts whose plan includes webhooks, and each plan sets how many addresses you may hold. See pricing.