Minecraft Java Edition
Overview
Connect your Minecraft Java server to topgservers so players who vote on the site automatically receive in-game rewards. The plugin periodically checks the vote-check API and listens for real-time webhooks to grant items, ranks, or currency instantly.
Prerequisites
- A Minecraft server running Bukkit, Spigot, or Paper (1.16+)
- Java 17 or newer
- A topgservers API key — generate one in My Servers → API
- Your webhook secret (optional, for real-time rewards)
- Port 443 outbound access from your server for HTTPS calls
Installation
1 Create the plugin structure
Create a new directory `TopGVote` inside your `plugins/` folder with the standard Bukkit layout.
plugins/TopGVote/
├── src/main/java/com/topgservers/vote/
│ ├── TopGVotePlugin.java
│ ├── VoteCheckTask.java
│ └── WebhookListener.java
├── src/main/resources/
│ ├── plugin.yml
│ └── config.yml
└── pom.xml (or build.gradle) 2 Add the plugin.yml
Register the plugin with Bukkit.
name: TopGVote
version: 1.0.0
main: com.topgservers.vote.TopGVotePlugin
api-version: "1.16"
description: topgservers vote reward integration
commands:
topgvote:
description: Check your vote status
usage: /topgvote
permission: topgvote.check 3 Implement the main plugin class
Load config, schedule the vote-check polling task, and optionally start the webhook HTTP listener.
package com.topgservers.vote;
import org.bukkit.plugin.java.JavaPlugin;
public class TopGVotePlugin extends JavaPlugin {
private String apiKey;
private String webhookSecret;
@Override
public void onEnable() {
saveDefaultConfig();
apiKey = getConfig().getString("api-key", "");
webhookSecret = getConfig().getString("webhook-secret", "");
if (apiKey.isEmpty()) {
getLogger().warning("No API key set! Edit plugins/TopGVote/config.yml");
return;
}
// Poll every 60 seconds (1200 ticks)
new VoteCheckTask(this, apiKey)
.runTaskTimerAsynchronously(this, 100L, 1200L);
if (!webhookSecret.isEmpty()) {
int port = getConfig().getInt("webhook-port", 8090);
new WebhookListener(this, webhookSecret, port).start();
}
getLogger().info("TopGVote enabled — polling + webhook ready");
}
} 4 Implement the vote check task
This async task calls the `/api/v1/vote-check` endpoint for each online player and rewards those who voted.
package com.topgservers.vote;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.net.URI;
import java.net.http.*;
import java.util.*;
public class VoteCheckTask extends BukkitRunnable {
private final TopGVotePlugin plugin;
private final String apiKey;
private final HttpClient http = HttpClient.newHttpClient();
private final Set<String> rewarded = new HashSet<>();
public VoteCheckTask(TopGVotePlugin plugin, String apiKey) {
this.plugin = plugin;
this.apiKey = apiKey;
}
@Override
public void run() {
for (Player player : Bukkit.getOnlinePlayers()) {
String name = player.getName();
if (rewarded.contains(name)) continue;
try {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(
"https://topgservers.net/api/v1/vote-check?username="
+ name))
.header("Authorization", "Bearer " + apiKey)
.GET().build();
HttpResponse<String> res =
http.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() == 200
&& res.body().contains("\"voted\":true")) {
rewarded.add(name);
// Run reward on main thread
Bukkit.getScheduler().runTask(plugin, () -> {
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"give " + name + " diamond 3"
);
player.sendMessage(
"§aThanks for voting on topgservers! §7+3 Diamonds"
);
});
}
} catch (Exception e) {
plugin.getLogger().warning(
"Vote check failed for " + name + ": " + e.getMessage()
);
}
}
}
} Configuration
# topgservers Vote Plugin Configuration
api-key: "tgs_your_api_key_here"
webhook-secret: "whsec_your_secret_here"
webhook-port: 8090
# Reward command — {player} is replaced with the player's name
reward-command: "give {player} diamond 3"
# Cooldown in hours before a player can be rewarded again
reward-cooldown: 24
# Message sent to the player after reward
reward-message: "&aThanks for voting on topgservers! &7You received 3 diamonds." Vote Check
Call the /api/v1/vote-check endpoint to determine if a player has voted today.
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(
"https://topgservers.net/api/v1/vote-check?username=" + playerName))
.header("Authorization", "Bearer " + apiKey)
.GET().build();
HttpResponse<String> res =
http.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() == 200 && res.body().contains("\"voted\":true")) {
// Player has voted today — grant reward
} Webhook Receiver
Verify the X-TopG-Signature header using HMAC-SHA256 to ensure webhook payloads are authentic.
package com.topgservers.vote;
import com.sun.net.httpserver.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class WebhookListener {
private final TopGVotePlugin plugin;
private final String secret;
private final int port;
public WebhookListener(TopGVotePlugin plugin, String secret, int port) {
this.plugin = plugin;
this.secret = secret;
this.port = port;
}
public void start() {
try {
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/webhook", exchange -> {
if (!"POST".equals(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(405, -1);
return;
}
String body = new String(
exchange.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8
);
String sigHeader = exchange.getRequestHeaders()
.getFirst("X-TopG-Signature");
if (sigHeader == null || !verifySignature(body, sigHeader)) {
exchange.sendResponseHeaders(401, -1);
return;
}
// Parse username from JSON body (use Gson in production)
String username = extractField(body, "username");
if (username != null) {
plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getServer().dispatchCommand(
plugin.getServer().getConsoleSender(),
"give " + username + " diamond 3"
);
});
}
exchange.sendResponseHeaders(200, -1);
});
server.setExecutor(null);
server.start();
plugin.getLogger().info("Webhook listener started on port " + port);
} catch (IOException e) {
plugin.getLogger().severe("Failed to start webhook listener: " + e.getMessage());
}
}
private boolean verifySignature(String body, String sigHeader) {
try {
String[] parts = sigHeader.split(",");
String timestamp = "", hash = "";
for (String part : parts) {
if (part.startsWith("t=")) timestamp = part.substring(2);
if (part.startsWith("v1=")) hash = part.substring(3);
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] expected = mac.doFinal(
(timestamp + "." + body).getBytes(StandardCharsets.UTF_8));
String expectedHex = bytesToHex(expected);
return expectedHex.equals(hash);
} catch (Exception e) {
return false;
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02x", b));
return sb.toString();
}
private static String extractField(String json, String field) {
int idx = json.indexOf("\"" + field + "\"");
if (idx == -1) return null;
int start = json.indexOf(":", idx) + 1;
int qStart = json.indexOf("\"", start) + 1;
int qEnd = json.indexOf("\"", qStart);
return json.substring(qStart, qEnd);
}
} Reward Examples
// Diamonds
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"give " + player.getName() + " diamond 3");
// Economy (Vault)
economy.depositPlayer(player, 500.0);
// Permission group (LuckPerms)
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"lp user " + player.getName() + " parent addtemp vip 7d");
// Custom item via Essentials
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"essentials:give " + player.getName() + " vote_crate_key 1"); Notes & Tips
The vote-check endpoint is designed for high-frequency polling (120 req/min). For large servers (100+ concurrent players), consider batching checks every 2–3 minutes instead of every minute. The webhook approach is more efficient for instant rewards and doesn't count against your rate limit.