GROUPSPACE
Developers · webhooks

Webhooks

Outbound events to your Discord or to an endpoint of your own, and the inbound erasure webhook that has to stay available whether or not you ever look at it.

Last updated 10 August 2026

Overview

Two directions, and they have nothing to do with each other beyond the word.

  • Outbound. GROUPSPACE tells your Discord, or an endpoint you run, that something happened in the workspace. Optional, per workspace, per event type.
  • Inbound. Roblox tells GROUPSPACE that a user has exercised their right to erasure. Not optional, not configurable, and covered at the bottom of this page.

Outbound webhooks sit alongside the other notification channels rather than replacing them. A workspace member can receive a given notification type in the app, by email, or through Discord, and the preference is per type rather than all-or-nothing.

The launch set is deliberately small. Outbound webhooks ship minimal: a short list of events, a Discord target, and a generic signed target. The event list below is the intended launch scope and may be trimmed before general availability. Nothing here is a stable long-term contract yet — build against it, but expect additions and check the release notes before you depend on a specific field.

Events

EventFires whenTypical use
record.filedA record is filed against a user — an arrest, a warning, a note, a flag, a commendation.A moderation channel that sees enforcement as it happens instead of at the weekly review.
operation.startedA scheduled operation advances into its running stage.Pinging the roster channel when the flight, patrol or training actually begins.
application.submittedSomeone submits an application against a job listing.Telling the hiring team there is something in the queue.
payout.run.approvedA payout run is approved and ready to settle.Handing the finance channel the signal to go and settle it.
quota.violatedA member closes a quota period below requirement.Command staff seeing the shortfall on the day rather than at period close.

Envelope

Every outbound event to a generic endpoint uses the same envelope. The data object varies by type.

POST your-endpoint HTTP
{
  "id": "evt_3c81f0ab",
  "type": "record.filed",
  "workspaceId": "ws_9d2f",
  "occurredAt": "2026-08-10T14:07:45Z",
  "data": {
    "recordId": "rec_41ba",
    "recordType": "arrest",
    "subject": { "platform": "roblox", "externalId": "1234567" },
    "filedBy": { "userId": "usr_77c1" },
    "orgId": "org_lspd"
  }
}
  • Treat id as an idempotency key. A redelivery reuses it, so store it and ignore duplicates.
  • Ignore fields you do not recognise. New fields will be added inside the same event type.
  • Events carry references, not payloads. A record webhook gives you the record id, not the record body — visibility rules live in GROUPSPACE and a webhook is not a way around them.
Webhooks do not bypass visibility. A record with restricted visibility fires the same event with the same reference, and fetching the detail still requires a permitted actor. Do not design a Discord bot that assumes it can read everything it hears about.

Discord webhooks

Discord is a first-class target rather than an afterthought, because it is how these communities actually consume notifications. It is also the cheapest of the three channels to run, which is why it ships at launch alongside in-app and email.

Setting one up

  • Create a webhook on the Discord channel you want, in that channel’s integration settings, and copy the URL.
  • Paste it into the workspace notification settings and pick the event types that should go to it.
  • Point different event types at different channels. Enforcement in one, hiring in another, payouts somewhere only finance can see.

What arrives

GROUPSPACE posts a formatted embed rather than the raw envelope — a title, the subject, the actor, the organization, and a link back into the workspace. The generic envelope above is what a signed endpoint of your own receives; Discord gets something a human can read in a channel.

A Discord webhook URL is a credential. Anyone holding it can post to that channel as your integration. Discord does not sign outbound posts, so there is no verification on that side — treat the URL as a secret, use a channel the right people can see, and rotate it if it leaks.
Discord rate limits are Discord’s. A very chatty workspace can be throttled by Discord itself. High-volume event types are better pointed at a signed endpoint you control, with Discord reserved for the things a human should actually react to.

Signed endpoints

For anything that is not Discord, register an HTTPS URL and GROUPSPACE will POST the envelope to it, signed with the same scheme the game API uses in the other direction.

Headers on every delivery HTTP
X-GS-Timestamp: 1786310400                    unix seconds
X-GS-Signature: hex(hmac_sha256(secret, timestamp .. rawBody))
X-GS-Event: record.filed
X-GS-Delivery: dlv_66a2c410
Content-Type:  application/json

Verifying

  • Compute the HMAC over the timestamp concatenated with the raw body bytes, before any JSON parsing. A re-serialised body will not match.
  • Compare in constant time. A naive string comparison leaks the signature a byte at a time.
  • Reject anything with a timestamp more than 300 seconds from your own clock.
  • Keep a short-lived cache of signatures you have already accepted and reject repeats inside that window.
  • Verify before you parse, and parse before you act. An unverified body is attacker-controlled input.

Responding

  • Any 2xx is success. Everything else is a failure and will be retried.
  • Answer quickly and do the work afterwards. Acknowledge, enqueue, return — do not hold the connection open while you process.
  • Deliveries are at-least-once. Deduplicate on id.
  • Ordering is not guaranteed. Two events about the same subject can arrive out of order, so use occurredAt rather than arrival order.

Retries and failure

A failed delivery is retried with exponential backoff and jitter across a bounded number of attempts. After that the delivery is abandoned and recorded as failed rather than retried forever.

  • A 4xx that is clearly permanent — 401, 404, 410 — is not retried. Something is misconfigured and retrying will not fix it.
  • A 5xx, a timeout or a connection error is retried.
  • An endpoint that fails continuously is marked unhealthy and surfaced in the workspace, so an operator finds out from their own settings screen rather than from the silence.
  • Failed deliveries are visible with their status code and response, which is usually enough to tell a bad URL from a bad handler.
The exact retry schedule is not final. The number of attempts, the backoff curve and the point at which an endpoint is disabled are still being tuned. Do not build logic that assumes a specific number of retries or a specific interval. Build an idempotent handler and the schedule stops mattering.

Right to erasure

Roblox sends a RightToErasureRequest webhook when a user exercises their right to have their data deleted. GROUPSPACE operates the handler. This section is here because you need to understand what it does to data your game depends on, and because operators keep asking whether they should build their own.

Do not build a player-facing erasure endpoint. The only erasure requests that get honoured are the ones that arrive from the platform, signed, through this webhook. A request a player sends you directly — in a Discord DM, in a form, in a message to your game — is not an erasure request. It is an unauthenticated instruction to delete someone’s data, and honouring it is how you delete the wrong person’s record on someone else’s say-so.

How the handler is built

RequirementImplementation
VerifiedHMAC signature check on every request, before parsing. Unsigned or badly signed requests are rejected outright.
Replay-protectedA 300-second acceptance window plus a replay cache. A resent request outside the window is not honoured.
Provider-originated onlyOnly requests that arrive through the platform webhook with a valid signature. There is no manual override that reaches this path.
Continuously availableThe endpoint must be reachable at all times, not only during business hours. It is monitored separately from the rest of the API.
AuditableEvery erasure is written to the audit log with what was purged, so a request can be shown to have been honoured without keeping the data that proves it.

What gets purged

For the supplied user id, across every workspace in the deployment that holds data for them: records, game sessions, chat messages and operation participation. This is deletion, not anonymisation, and it is not reversible.

  • A record filed against an erased user is removed, which means moderation history for that user disappears. That is the correct outcome and it is not a bug report.
  • Aggregate figures that were already rolled up may still reflect the session that was deleted. Aggregates carry no user id and are not personal data.
  • Erasure is independent of bans. A user who was banned and then erased is no longer in the blocklist, because the blocklist entry is data about them.
  • All data is expunged on access revocation as well, not only on an explicit erasure request.

Platforms without an erasure webhook

The capability is a flag on the game provider, and it is true for Roblox and false elsewhere. Steam, FiveM and custom integrations have no equivalent platform signal, so erasure for those users runs through a manual request flow instead — same purge, same audit entry, different trigger.

If you store GROUPSPACE data outside GROUPSPACE — a mirror in your own database, a Discord bot that caches records, a spreadsheet — the erasure obligation follows the data. The webhook purges our copy. It cannot purge yours.