API Docs

Webhooks reference

AdminUpdated Sep 22, 2026

Webhooks reference

Compact reference for outbound webhooks. For setup walkthroughs, full payload examples and verification code, see Webhooks.

Base URL: https://api.coolgptgames.com. Auth: Authorization: Bearer <key> with the webhooks scope, or a signed-in dashboard session. See API keys & scopes.

Management endpoints

Method & path

Body

Success response

Notes

POST /v1/dev/webhooks

{ "url": string, "events"?: string[] }

201 { webhook: { id, url, events, secret }, availableEvents }

url: valid URL, https:// only, ≤ 500 chars. Leave events out or send [] to subscribe to all (stored as ["*"]). "*" isn't accepted inside events. secret is returned only here.

GET /v1/dev/webhooks

200 { webhooks: [{ id, url, events, active, at }], availableEvents }

Newest first. at = creation time. No secrets.

DELETE /v1/dev/webhooks/:id

200 { ok: true }

Stops deliveries immediately and removes the delivery log

GET /v1/dev/webhooks/:id/deliveries

200 { deliveries: [{ id, webhookId, event, statusCode, ok, error, createdAt }] }

The 25 most recent attempts, newest first

POST /v1/dev/webhooks/:id/test

200 { ok: true }

Sends webhook.test to this webhook only, whatever its event list. ok means the attempt was made, not that your server accepted it; check the delivery log.

There are no endpoints to update a webhook, rotate its secret, pause it or redeliver an event.

Errors

Status

error.code

When

400

validation_error

Bad url (not https, invalid, too long) or an unknown event name in events

400

too_many_webhooks

You already have 10 webhooks

401

unauthorized

Missing or invalid credentials

403

forbidden

The key lacks the webhooks scope, or the webhook belongs to another account

404

not_found

No webhook with that ID

Errors use the standard envelope { "error": { "code", "message", "details"? } }. See Errors & limits.

Limits

Limit

Value

Webhooks per account

10

URL

https:// only, ≤ 500 characters

Delivery timeout

6 seconds

Attempts per event per webhook

1 (no retries)

Delivery log shown

25 most recent

Event catalogue

Deliveries only cover your own games and the tournaments you created.

Event

Sent when

data fields

game.approved

A version of your game is approved, by the automated pipeline or a human moderator

gameId, gameVersionId, slug, title, reasonCodes: string[], automated: boolean

game.rejected

A version of your game is rejected, by the automated pipeline or a human moderator

gameId, gameVersionId, slug, title, reasonCodes: string[], automated: boolean

game.reported

A player files an abuse report on your game

gameId, reasonCode

review.created

A player submits or edits a rating with written text

gameId, rating (1–5), title (string or null), body

tournament.ended

A tournament you created settles (a background job checks every 30 seconds; viewing it after its end time also settles it)

tournamentId, name, gameId, winners: [{ userId, rank, prizeCoins }]

payout.paid

Subscribable, but not sent at this time

webhook.test

You trigger a test for one webhook (not subscribable by name; sent only to that webhook)

message

automated is true for a pipeline decision and false for a human one. A version held for human review sends nothing until a moderator decides. A rejected update to a live game only rejects that version; the game stays published on its previous version. Canceled tournaments don't send tournament.ended. In winners, entrants who never scored are left out, and players tied on the same result share a rank and split the prizes for the places they occupy.

reasonCode / reasonCodes values: nsfw_sexual, nsfw_nudity, graphic_violence, gore, hate_symbols, harassment, ip_infringement, trademark, broken_not_playable, blank_screen, not_a_game, low_effort_duplicate, exact_duplicate, malicious_code, cryptominer, data_exfiltration, phishing_ui, misleading_metadata, spam, contact_info_spam, gambling, age_inappropriate, economy_abuse, other.

prizeCoins are virtual coins, not real money.

Request

POST <your url>
content-type: application/json
x-arcadey-event: <event>
x-arcadey-signature: sha256=<64 lowercase hex chars>

Body (compact JSON, keys in this order):

{ "event": "<event>", "data": { }, "sentAt": "<ISO 8601 UTC>" }

Signature algorithm

Item

Value

Header

x-arcadey-signature

Format

sha256= + lowercase hex digest

Algorithm

HMAC-SHA256

Key

The full secret string as UTF-8, including the whsec_ prefix

Message

The raw request body bytes, exactly as received

Timestamp in signature

None. Use the signed sentAt field for replay checks.

Comparison

Constant-time (crypto.timingSafeEqual / hmac.compare_digest)

// Node.js
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
# Python
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()

Secrets look like whsec_ + 48 hex characters, and each webhook has its own.

Delivery and retry schedule

Item

Value

Success

Any 2xx response within 6 seconds. Your response body is ignored.

Failure

Non-2xx, timeout, or a network/TLS error

Retries

None. Each event is attempted once per webhook.

Backoff

Not applicable

Manual redelivery

Not available

Ordering

Not guaranteed. Subscribed webhooks receive the event in parallel.

Delivery or event ID

None. Deduplicate on payload fields.

Logged per attempt

event, statusCode (null if there was no response), ok, error (≤ 300 chars), createdAt

Caveats

  • Dashboard with no events ticked: creates an all-events webhook, the same as leaving out events in the API.

  • Secret rotation: create a second webhook for the same URL, accept either secret, then delete the old one. Each event arrives twice during the overlap.

Was this page helpful?