Quick answer: Check platform ban status and game-side ban status at every session start. Propagate platform bans but keep your own ban decisions independent. Show the player a specific, localized message explaining the ban and the appeal path. Keep evidence audit logs so support can make informed decisions when appeals arrive.

When a player gets banned from Steam, Xbox Live, or PlayStation Network, your game has to decide what to do with them. Do you reject their connection at the door? Silently accept it and let them play solo? Match them only with other banned players? Each decision has tradeoffs, and getting it wrong leads to either leaking cheaters into the playerbase or wrongly punishing legitimate players who got caught by a platform’s overzealous automation. Building a clear ban handling pipeline is one of the less glamorous parts of multiplayer engineering, but it pays off the first time a streamer gets wrongly banned and posts about it.

Two Sources of Truth

Every multiplayer game has two ban lists: the platform’s and yours. The platform ban comes from Steam, Microsoft, or Sony; they tell you via their backend API or the client ticket payload. Your ban comes from your own moderation and anticheat systems. These lists are related but not identical. A player banned on Steam for a non-game offense should probably still be able to appeal through you. A player banned on your service for harassment should not be automatically unbanned when Steam decides something unrelated.

At session start, query both sources:

async def authenticate_session(ticket: PlatformTicket) -> AuthResult:
    identity = await platform.verify(ticket)
    if not identity.ok:
        return AuthResult.reject(code="TICKET_INVALID")

    # Platform-level ban: account suspended from Steam/Xbox/PSN
    if identity.platform_ban_active:
        return AuthResult.reject(code="PLATFORM_BAN",
                                  expires_at=identity.platform_ban_expires)

    # Game-side ban from our moderation system
    game_ban = await db.fetch_ban(identity.account_id)
    if game_ban and game_ban.active:
        return AuthResult.reject(code=game_ban.code,
                                  expires_at=game_ban.expires_at,
                                  appeal_id=game_ban.appeal_id)

    return AuthResult.accept(identity=identity)

Keep the rejection reasons distinct. Mixing “platform banned you” with “we banned you” confuses support staff and makes appeals impossible to route.

Propagation Policy

When a player gets a platform-level VAC ban or Xbox enforcement action, you have to decide whether that translates into a ban on your service. Most studios land on a middle ground: platform bans from the same game (VAC in your title) become game-side bans automatically, but unrelated platform bans (payment fraud, chat abuse in another game) do not.

Write the propagation rule in code rather than in a runbook. It needs to be testable and consistent:

func ShouldPropagate(b PlatformBan) bool {
    switch b.Kind {
    case VACBanThisGame:
        return true  // cheater in our title: ban them
    case GameBanThisGame:
        return true
    case CommunityBan, AccountSuspend:
        return false // unrelated: let them play
    case VACBanOtherGame:
        return false // their business, not ours
    }
    return false
}

Shadow Bans for Soft Enforcement

A full disconnect ban tells the player immediately that they are banned. For cheaters, this is bad because it lets them test fixes and come back cleaner. For borderline cases (suspected boosting, suspected toxicity that needs review), it is too severe.

Shadow bans solve both. A shadow-banned player sees a working game, finds matches, and plays out sessions — but the matchmaker puts them only in a pool of other shadow-banned accounts. They wonder why every opponent seems to hit them strangely often. From their side it looks like “the game got harder.” From your side it looks like contained damage.

public MatchQueue QueueFor(Player p) {
    if (p.ShadowBanActive) return MatchQueue.Quarantine;
    if (p.SkillTier == Tier.Pro)   return MatchQueue.Ranked;
    return MatchQueue.Casual;
}

Player Communication

The single most common support ticket about bans is “why was I banned?” followed by “the game just shows a network error.” If your client cannot distinguish between “server unreachable” and “banned,” players assume they were banned unfairly.

Show the player a specific message: the ban reason, the ban duration, when they can play again, and a link or in-client form for appealing. Localize it. Provide a ban ID so support can look up the evidence instantly when the appeal arrives. Never use a generic modal.

Evidence Audit Trail

Every ban should be backed by structured evidence stored alongside the ban record. For anticheat bans, store the match ID, the cheat signal fingerprint, and the client integrity hash. For moderator bans, store the reporter IDs, the chat log context, the moderator’s user ID, and a written justification. When a player appeals, support staff can load the ban record and see exactly why it was issued without guesswork.

CREATE TABLE bans (
    id            BIGINT PRIMARY KEY AUTO_INCREMENT,
    account_id    CHAR(36) NOT NULL,
    kind          ENUM('cheat','toxicity','fraud','manual'),
    scope         ENUM('full','shadow','chat') NOT NULL,
    issued_at     DATETIME NOT NULL,
    expires_at    DATETIME NULL,
    issued_by     VARCHAR(64) NOT NULL, -- "anticheat", "mod:alice"
    evidence_json JSON NOT NULL,
    appeal_id     CHAR(36) NULL,
    overturned    BOOLEAN DEFAULT FALSE,
    overturned_by VARCHAR(64) NULL
);

Appeals as a First-Class Workflow

Build appeals into the game, not into a web form nobody finds. The appeal should take 30 seconds: player types a paragraph of context, their account ID auto-fills, the ban reason and evidence IDs auto-fill. Queue it in a support backlog with the original evidence already attached.

Track the appeal overturn rate per ban source. If your anticheat is at 2 percent overturn, you’re fine. If it’s at 30 percent, your detection is wrong and you should dial it back. Visible overturn rates are a hedge against your own false positives.

“We shipped a new anticheat signal and the overturn rate on bans triggered by it hit 40 percent in the first week. Catching that number early meant we disabled the signal and apologized to thirty players before it became a public relations problem.”

Related Issues

For multiplayer stability in general, see bug reporting for multiplayer games. For handling related chaos scenarios, read how to set up chaos testing for game servers.

Audit your ban error messages today. If any of them say “network error” or “connection lost” when the actual reason is a ban, fix that before anything else.