Webhooks
Webhooks are server-to-server callbacks that GamerSafer sends to your backend — you do not call these endpoints. When an identity event occurs (a player joins your guild, completes a document review, or passes a re-authentication check), GamerSafer will POST the event payload to a URL on your server.
This is the inverse of the API Reference: instead of your backend calling GamerSafer, GamerSafer calls you.
Your backend ──── POST /guilds/invites ────► GamerSafer API
Your backend ◄─── POST /your-webhook-url ─── GamerSafer APIReceiving Webhooks
Set up your endpoint
Your webhook endpoint must:
- Accept
POSTrequests with a JSON body (Content-Type: application/json) - Return HTTP
200as quickly as possible — do not perform slow operations synchronously in the handler - Handle the same event being delivered more than once (idempotency) — GamerSafer may retry on network failures
- Log all incoming payloads for debugging and auditing
// Example: minimal Express handler
app.post('/webhooks/gamersafer', express.json(), (req, res) => {
res.sendStatus(200); // Acknowledge immediately
processEvent(req.body); // Handle async
});Provide your URL
You provide the webhook URL in two ways depending on the event type:
| How | Events delivered |
|---|---|
webhookUrl parameter in each API request | guildInvite, playerVerification |
| Guild-level URL configured during onboarding | leaveGuild, documentValidated, documentRejected |
Security
GamerSafer does not currently sign webhook payloads with a cryptographic signature. To protect your endpoint:
- Keep the URL non-obvious — treat it like a secret path (e.g., include a random token in the URL path)
- Validate the
eventfield before processing — reject unexpected event names - Do not expose the URL in client-side code
Webhook signature verification is on the GamerSafer roadmap. This page will be updated when it is available.
Events
guildInvite — Player joined your guild
{
"event": "guildInvite",
"payload": {
"code": "123456",
"guildMemberId": "01FC3GW10QG03E3FH3Q5ZPAYEN",
"internalId": "your-internal-player-id"
},
"ageGroup": {
"label": "ADULT",
"confidence": "HIGH",
"genderPrediction": "MALE"
},
"player": {
"id": "01FC3GW10QG03E3FH3Q5ZPAYZ"
}
}What to do: store guildMemberId in your database linked to your internal player record. The ageGroup field provides a confidence-scored age classification derived from the document check (ADULT / MINOR).
leaveGuild — Player left your guild
{
"event": "leaveGuild",
"payload": {
"guildMemberId": "01FC3GW10QG03E3FH3Q5ZPAYEN",
"guildId": "01FC3GW10QG03E3FH3Q5ZPAYZZ"
}
}What to do: update your database to reflect that this player is no longer a guild member. Future access to gated features should trigger a new guild invite flow.
documentValidated / documentRejected — Document review result
{
"event": "documentValidated",
"payload": {
"guildMemberId": "01FC3GW10QG03E3FH3Q5ZPAYEN",
"type": "RG",
"documentNumber": "3423432",
"documentRejectionReason": "Rejection reason, or undefined if validated",
"dateOfBirth": "1988-10-06T00:00:00.000Z",
"name": "João da Silva"
}
}What to do: update your database with the document verification status. If approved, unlock the gated feature for this player. If rejected, prompt the player to resubmit via the Documents deep link.
playerVerification — Re-authentication result
{
"event": "playerVerification",
"payload": {
"guildMemberId": "01FC3GW10QG03E3FH3Q5ZPAYEN",
"verified": true
}
}What to do: match guildMemberId to your pending verification request. If verified is true, proceed with the authorized action (approve the withdrawal, mark the match as valid, etc.). If false, or if the webhook never arrives before expiresAt, treat it as a failed re-authentication.