Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/VasquezRivero92/Discord_Faceit/llms.txt

Use this file to discover all available pages before exploring further.

The bot’s webhook endpoint receives real-time events from your Faceit Hub — match status transitions, results, and club role changes — and translates them into Discord actions: announcements, voice channels, role updates, and match cleanup. Everything the bot does in response to matches flows through this single endpoint.

Endpoint

POST /webhook/faceit
  • Rate limit: 60 requests per minute (stricter than the global 100 req/min limit applied to all other routes).
  • Response: 200 OK is sent immediately before the event is processed. Event processing is async — Faceit does not need to wait for Discord API calls to complete.

Setup Steps

1

Open Faceit Hub Webhook Settings

Log in to your Faceit account, navigate to your Hub dashboard, and go to Settings → Webhooks → Add new webhook.
2

Enter the Webhook URL

Set the URL to:
https://your-domain/webhook/faceit
Your domain must be publicly reachable over HTTPS — Faceit will not deliver to http:// endpoints or private IPs.
3

Subscribe to Events

Enable these event subscriptions:
  • match_status_configuring
  • match_status_starting
  • match_status_ready
  • match_status_finished
  • match_status_cancelled
  • match_status_aborted
  • hub_user_role_added
  • hub_user_role_removed
4

Copy the Webhook Secret

After saving, Faceit provides a webhook secret. Set it in your environment:
FACEIT_WEBHOOK_SECRET=your-secret-here
5

Enable Signature Validation

In the Admin Panel → Features tab, enable the webhookValidation flag. Once enabled, the bot rejects any webhook request whose x-faceit-signature or x-webhook-signature header does not match FACEIT_WEBHOOK_SECRET.

Signature Validation

When webhookValidation is enabled and FACEIT_WEBHOOK_SECRET is set, the bot performs header validation before processing any event:
const validationEnabled = await isFeatureEnabled('webhookValidation');
if (validationEnabled && env.FACEIT_WEBHOOK_SECRET) {
  const signature = req.headers['x-faceit-signature'] || req.headers['x-webhook-signature'];
  if (signature !== env.FACEIT_WEBHOOK_SECRET) {
    logger.warn('Webhook rechazado: firma inválida');
    return res.status(401).json({ error: 'Unauthorized' });
  }
}
Requests with a missing or mismatched signature return 401 Unauthorized. See Feature Flags for how to toggle webhookValidation.

Event Routing

The bot extracts the Hub ID from event.entity.id (falling back to event.payload.hub_id) and looks up which Discord guilds are configured for that hub:
const webhookHubId = event.entity?.id || event.payload?.hub_id || event.payload?.id;
const guildIds = await db.getGuildIdsByHubId(webhookHubId);
If no guilds are found for that Hub ID, the event is silently ignored. If multiple guilds share the same Hub ID (rare but supported), the event is processed once for each matching guild using its own getEffectiveConfigForGuild(guildId).

Handled Events

Triggered when a match is created and players are being assembled.Bot actions:
  1. Creates a match embed with status configuring using createMatchEmbed(matchData, 'configuring').
  2. Posts the embed to the discordMatchesChannelId channel.
  3. Stores the match in Firestore faceit_active_matches with message ID, faction rosters, and hub/guild association via db.addActiveMatch(...).
  4. Updates the live match panel embed via updateLivePanel(...).
Feature flags checked: matchAnnouncements
Triggered when the match server is ready and the match has begun.Bot actions:
  1. Creates voice channels for both factions (if autoVoiceChannels is enabled) via createMatchVoiceChannels(guild, waitingRoomId, faction1, faction2, db).
  2. Moves players to their respective voice channels (if autoMovePlayers is enabled) via movePlayersToVoice(...).
  3. Builds team lists with Discord mentions for all players that have linked accounts.
  4. Edits the configuring embed to the started state, or sends a new one if the original message is not found.
  5. Posts a server status embed to discordStatusChannelId (if serverStatusAnnouncements is enabled), showing LATAM server health for the match location.
  6. Updates the live match panel.
Feature flags checked: matchAnnouncements, autoVoiceChannels, autoMovePlayers, serverStatusAnnouncements
Triggered when the match ends with a result.Bot actions:
  1. Deletes both team voice channels and moves players back to the waiting room via cleanupVoiceChannel(...).
  2. Builds final team lists with Discord mentions (using stored rosters from Firestore if the webhook payload has empty rosters).
  3. Edits the match embed to finished state with winner and score.
  4. Removes the match from faceit_active_matches via db.removeActiveMatch(matchData.id).
  5. Saves the result to faceit_matches_history via db.addMatchToHistory(...) with winner, score, and rosters.
  6. Syncs Faceit levels for all players in both factions if autoRoleSync is enabled — calls the Faceit API per player and updates Discord roles.
  7. Updates the live match panel.
Feature flags checked: matchAnnouncements, autoRoleSync
Triggered when a match is cancelled before completion.Bot actions:
  1. Deletes both team voice channels and moves players back to the waiting room.
  2. Edits (or sends) the match embed with cancelled status.
  3. Removes the match from faceit_active_matches.
  4. Logs a warning-level entry via db.addLogEntry('warn', ...).
  5. Updates the live match panel.
Feature flags checked: matchAnnouncements
Triggered when a Faceit Hub admin grants a special club role to a player.Bot actions:
  1. Maps the Faceit role_id to a Discord role name using CLUB_ROLES_MAP (supports EXTREME, PREMIUM, PLUS, EXTREME BAJOS).
  2. Looks up the player’s Discord ID from their Faceit Player ID in Firestore.
  3. Gets or creates the matching Discord role in the guild via getOrCreateSpecialRole(guild, roleName).
  4. Adds the role to the Discord member and posts a notification to discordRolesChannelId.
Feature flags checked: autoRoleSync
Triggered when a special club role is removed from a player.Bot actions:
  1. Same lookup as hub_user_role_added.
  2. Removes the corresponding Discord role from the member.
  3. Posts a notification to discordRolesChannelId.
Feature flags checked: autoRoleSync

Example Webhook Payload

{
  "event": "match_status_finished",
  "entity": { "id": "<hub-uuid>" },
  "payload": {
    "id": "<match-uuid>",
    "game": "cs2",
    "teams": {
      "faction1": {
        "name": "Team A",
        "roster": [
          { "player_id": "abc123", "nickname": "Player1" },
          { "player_id": "def456", "nickname": "Player2" }
        ]
      },
      "faction2": {
        "name": "Team B",
        "roster": [
          { "player_id": "ghi789", "nickname": "Player3" },
          { "player_id": "jkl012", "nickname": "Player4" }
        ]
      }
    },
    "results": {
      "winner": "faction1",
      "score": { "faction1": 16, "faction2": 8 }
    },
    "voting": {
      "map": { "pick": ["de_mirage"] },
      "location": { "pick": ["lima"] }
    }
  }
}
The entity.id field is the Hub UUID — this is what db.getGuildIdsByHubId() uses to route the event to the correct Discord guild. Ensure the Hub ID in this field matches the faceitHubId value in your bot config.

Testing Webhooks

Use the Simulator tab in the Admin Panel to send mock webhook payloads to the bot without a real Faceit Hub match. The simulator constructs a valid payload using your current linked users as roster members and calls POST /api/admin/simulate-webhook which forwards it to the internal webhook handler.

Health Check

GET /health
Returns the bot’s current status — useful for uptime monitors and confirming the server is live before configuring the Faceit webhook:
{
  "status": "ok",
  "discord": "connected",
  "uptime": 3742.5,
  "timestamp": "2024-11-01T12:00:00.000Z"
}
FieldDescription
statusAlways "ok" if the Express server is running
discord"connected" if the Discord client has logged in, "disconnected" otherwise
uptimeProcess uptime in seconds (process.uptime())
timestampCurrent ISO timestamp

Build docs developers (and LLMs) love