Home / Developers / Minecraft Java Edition
⛏️

Minecraft Java Edition

Java 17 Bukkit / Spigot / Paper

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.

Directory structure
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.

plugin.yml
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.

TopGVotePlugin.java
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.

VoteCheckTask.java
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

config.yml
# 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.

VoteCheckTask.java (core logic)
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.

WebhookListener.java
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

Reward commands
// 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.