Minecraft: Chargeback Alerts
When a player files a chargeback against a purchase on your Minecraft server, the bot reacts immediately: it strips the player’s Discord roles, applies a “restricted” role, and posts a staff alert with Ban and Dismiss buttons. Staff can ban (Discord + Minecraft) or dismiss with a click.
This closes the loop on the donor flow: verify creates the Discord ↔ Minecraft mapping, donator sync keeps roles in line with active donations, and chargeback alerts handle the case where a donation gets reversed.
What it does
The MC store (Tebex, Buycraft, or whatever you use) is wired up to
POST a chargeback notification to the bot’s HTTP listener at
/webhook/chargeback. On receipt:
- Authenticates the request via
Authorization: Bearer <MC_VERIFY_SECRET>. Mismatched or missing tokens get a401 Unauthorizedand no further processing. - Strips and restricts. If the chargeback payload includes a
linked
discord_id, the bot callsedit_memberon Discord with a fresh roles list of just[restricted_role]— wiping every other role the user had — and an audit-log reasonChargeback: roles stripped, user restricted. - Posts a staff alert to the configured staff channel, showing the player’s MC username, tier, UUID, the linked Discord account (if any), and a timestamp. Two buttons sit at the bottom: 🔨 Ban and ❌ Dismiss.
- Returns 200 to the MC store so the chargeback notification isn’t retried.
The Ban button issues a Discord ban (if the user is linked) and
a Minecraft ban (via <MC_VERIFY_URL>/api/ban). Dismiss closes
the alert without further action. Either way the embed is
rewritten with a footer recording who took the action (“Banned
by alice” or “Dismissed by bob”) and the buttons are removed so
the alert can’t be acted on twice.
Activation
Chargeback alerts are part of the Minecraft module. You need:
[features]
minecraft = true
[minecraft]
chargeback = true
[minecraft.chargeback_config]
staff_channel = "123456789012345678"
restricted_role = "234567890123456789"
staff_roles = ["345678901234567890", "456789012345678901", "567890123456789012"]
Plus the standard MC env vars in .env:
MC_VERIFY_URL=https://your-mc-server.example.com
MC_VERIFY_SECRET=long-random-string-shared-with-the-mc-plugin
The chargeback listener is mounted onto the bot’s existing
HTTP server (the one the MCP server uses) at
POST /webhook/chargeback. There’s no separate port; the bot’s
HTTP listener handles both. See MCP Server for
how to expose the HTTP listener publicly.
The webhook router only spins up if all three preconditions hold:
the chargeback feature is enabled, the config sub-section is
present, and MC_VERIFY_URL + MC_VERIFY_SECRET are both set.
Missing any one of those quietly disables the route — the bot
starts cleanly, but no chargeback alerts will ever fire.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
staff_channel | string (snowflake) | yes | Channel ID where alerts are posted. |
restricted_role | string (snowflake) | yes | Role ID applied to the offender (and used as the only remaining role on their account after roles are stripped). |
staff_roles | list of string (snowflakes) | no | Roles allowed to press the Ban/Dismiss buttons on a chargeback alert. Defaults to an empty list, meaning no one is allowed (the buttons will deny every interaction with You don't have permission to do this.), so configure this if you want staff to be able to confirm or dismiss chargebacks. |
staff_channel and restricted_role must be valid snowflakes.
Invalid values cause the webhook to return 500 Internal Server Error and log the bad config; the underlying chargeback isn’t
lost on the MC side, but no alert gets posted on the Discord side
until you fix the config. Entries in staff_roles that don’t
parse as integers are silently skipped, so a typo will quietly
remove that role from the allowlist — double-check the IDs if
buttons aren’t working for staff who you expect to have access.
The restricted role’s purpose is twofold:
- It marks the user as “currently in a chargeback hold” so donator sync doesn’t re-grant their donor perks on the next tick.
- Combined with channel-level permissions on your server, it isolates the user from sensitive channels until staff resolves the alert.
Set the role’s permissions and channel overrides to whatever “banned but not yet acted on” means for your server.
The webhook payload
The MC plugin POSTs JSON in this shape:
{
"uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5",
"username": "Steve",
"discord_id": "987654321098765432",
"tier": "supporter",
"timestamp": "2026-04-15T18:00:00Z"
}
discord_id is optional — if the player never verified, it’s
null and the bot only takes MC-side action via the staff button.
The wire format lives in
src/minecraft/chargeback.rs::ChargebackPayload. Authentication
is the shared MC_VERIFY_SECRET sent as Authorization: Bearer <secret>; treat the secret like a credential.
The staff alert
The alert embed has a red border and the title
⚠️ CHARGEBACK ALERT, with fields for Player, Tier,
Discord (<@id> (id) or Not linked), MC UUID, and
Time. The footer summarizes the automatic action: All roles stripped. User restricted. (linked) or No Discord account linked. MC-side actions only. (unlinked).
Two buttons sit beneath: 🔨 Ban (or Ban MC if unlinked)
and ❌ Dismiss. Only members with one of the roles listed in
staff_roles (see Staff role gating below) can press either —
anyone else gets You don't have permission to do this.
After a button is pressed the embed is rebuilt with a neutral
border, the footer is replaced with Banned by <staff> or
Dismissed by <staff>, and the buttons are removed.
Ban action
When a staff member clicks Ban:
- Discord side. If the embed shows a linked
discord_id, the bot extracts it and callsban_userwith audit-log reasonChargeback ban by <staff>. Failures are logged but don’t block MC side. - MC side. The bot POSTs to
<MC_VERIFY_URL>/api/banwith the player’s UUID and a reason identifying the staff member. - Failure surfacing. If the MC ban returns non-2xx or
transport-fails, the bot reposts the failure into the staff
channel:
⚠️ MC ban failed for UUID {uuid}: {status} {body}.
Dismiss action
Clicking Dismiss doesn’t restore anything — the roles are already stripped and the restricted role applied. It just closes the alert with a footer recording who dismissed it. To restore a user, staff manually removes the restricted role and re-adds whatever roles they had before; stripped roles aren’t preserved.
Staff role gating
Button permission checks read the staff_roles list from
[minecraft.chargeback_config]. A member must hold at least one
of the listed roles to press either Ban or Dismiss; everyone
else gets You don't have permission to do this. as an ephemeral
reply.
The list is #[serde(default)], so omitting staff_roles
entirely (or leaving it as []) means no one can use the
buttons — the alert still posts and the auto-strip still
happens, but the alert can’t be confirmed or dismissed from
Discord. This is the safe default for a feature that’s opt-in
per instance: you must explicitly grant button access.
Add as many or as few roles as you want — typically Moderator, Admin, and Owner, but anything you wire up works:
staff_roles = ["111111111111111111", "222222222222222222", "333333333333333333"]
Entries that don’t parse as u64 are silently dropped at
button-handler time, so a malformed snowflake will quietly
remove that role from the allowlist without preventing the
others from working.
Permissions
The bot’s role must:
- Be higher than the restricted role and any roles it might need to strip on offenders, in the role hierarchy.
- Have
MANAGE_ROLES(to apply the restricted role and strip roles) andBAN_MEMBERS(for the Ban button). - Have
SEND_MESSAGESandEMBED_LINKSin the configuredstaff_channel.
Without BAN_MEMBERS the auto-strip will succeed but the Ban
button will fail; the alert will still post and dismiss.
Common issues
- No alert appears after a chargeback — check the bot logs
for
Chargeback webhook received: …. If absent, the MC plugin isn’t POSTing to the right URL or the HTTP listener isn’t exposed. If present but no embed posts, checkstaff_channeland bot permissions there. 401 Unauthorizedon the webhook —MC_VERIFY_SECRETdoesn’t match between bot and plugin.- Roles aren’t stripped on the offender — only happens when
the payload includes a linked
discord_id. Unverified users get no Discord-side action; the alert footer says so. - Ban button does nothing for non-staff — staff role gate.
Add the staff member’s role to
staff_rolesin[minecraft.chargeback_config]and restart the bot. Ifstaff_rolesis empty (the default), no one can press the buttons. - MC ban failed — the
/api/banendpoint returned an error. The bot reposts the failure into the staff channel; check the response body. - Dismiss didn’t restore the user’s roles — by design. Restoring stripped roles is a manual operation.
Cross-references
- Minecraft: Verify — for the Discord ↔ UUID mapping that determines whether chargeback can act Discord-side.
- Minecraft: Donator Sync — uses the restricted role as a “do not re-grant donor perks” signal.
- MCP Server — how the bot’s HTTP listener is hosted; the chargeback webhook lives on the same router.
- Instance Config:
[minecraft.chargeback_config]— schema reference. - Environment Variables —
MC_VERIFY_URL,MC_VERIFY_SECRET.