Vote rewards

Vote Rewards API

Integrate your Palworld server with voting rewards through webhook callbacks or polling endpoints.

Overview

The vote rewards system lets you grant in-game rewards after players vote for your server. Choose Push to receive a webhook immediately when a vote is cast, or Pull to have your plugin poll for available votes.

  • Push (webhook): PalworldServers.io sends a POST request to your callback URL.
  • Pull (polling): Your integration checks API endpoints and claims processed votes.

Base URL: https://palworldservers.io

Authentication

Version 1 vote endpoints authenticate your server with its server key. Find this key in your server's dashboard and keep it private: anyone with the key can access the vote endpoints for that server.

Vote Link

Send players to this URL, replacing both placeholders for your integration.

Vote URL
https://palworldservers.io/server/{serverId}/vote?player_id={PLAYER_ID}

The player_id from the URL is sent to your webhook and stored with the vote. When vote rewards are enabled and no player ID is provided, the voter is shown a modal to enter it.

Push: Webhook Callbacks

When a vote happens, we POST this payload to your configured callback URL.

Webhook payload
{
  "server": "server-id",
  "playerId": "PLAYER_ID",
  "user": "optional-website-user-id",
  "type": "vote",
  "timestamp": "2026-01-01T12:00:00.000Z"
}
HeaderValue
Content-Typeapplication/json
AuthorizationYour configured secret
X-Webhook-SignatureHMAC-SHA256 signature of the request body
User-AgentPalworldServers-Vote-Webhook/1.0

Verify the signature

Node.js
import crypto from 'crypto'

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  )
}

const isValid = verifySignature(
  rawRequestBody,
  req.header('X-Webhook-Signature'),
  process.env.WEBHOOK_SECRET
)
Python
import hashlib
import hmac

def verify_signature(raw_body, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

is_valid = verify_signature(
    request.get_data(),
    request.headers['X-Webhook-Signature'],
    os.environ['WEBHOOK_SECRET'],
)

Your endpoint must return a 2xx response after processing the event. Failed deliveries are not retried automatically; their failure count is tracked in the dashboard.

Pull: Polling API Endpoints

Poll these v1 endpoints from your plugin or service to process rewards.

GET/api/v1/vote/check/{serverKey}/{playerId}

Check whether a specific player has voted.

Response
{
  "voted": true,
  "lastVote": "2026-01-01T12:00:00.000Z",
  "canVote": false,
  "nextVote": "2026-01-02T00:00:00.000Z",
  "isClaimed": false
}
GET/api/v1/vote/unclaimed/{serverKey}?limit=50

Get unclaimed votes with player IDs. limit accepts 1–100 and defaults to 50.

Response
{
  "server": "server-id",
  "votes": [{ "id": "vote-id", "playerId": "PLAYER_ID", "votedAt": "2026-01-01T12:00:00.000Z" }]
}
POST/api/v1/vote/mark-claimed/{serverKey}

Mark votes as claimed only after granting their rewards. Submit at most 100 IDs.

Request body
{ "voteIds": ["vote-id-1", "vote-id-2"] }
Response
{ "success": true, "claimed": 2 }
GET/api/v1/vote/leaderboard/{serverKey}

Get the current month's vote leaderboard.

Response
{
  "server": { "id": "server-id", "name": "My Server" },
  "period": { "month": "2026-01" },
  "leaderboard": [{ "rank": 1, "username": "Player", "votes": 12, "isRegistered": true }]
}

Code Examples

Lua plugin polling
local http = require('socket.http')
local json = require('json')

local serverKey = 'YOUR_SERVER_KEY'
local playerId = 'PLAYER_ID'
local url = 'https://palworldservers.io/api/v1/vote/check/' .. serverKey .. '/' .. playerId

local body, status = http.request(url)
if status == 200 then
  local vote = json.decode(body)
  if vote.voted and not vote.isClaimed then
    grantVoteReward(playerId)
    -- Mark the vote as claimed with POST /mark-claimed/{serverKey}
  end
end
Python polling
import requests

SERVER_KEY = 'YOUR_SERVER_KEY'
player_id = 'PLAYER_ID'
url = f'https://palworldservers.io/api/v1/vote/check/{SERVER_KEY}/{player_id}'

response = requests.get(url, timeout=10)
response.raise_for_status()
vote = response.json()

if vote['voted'] and not vote['isClaimed']:
    grant_vote_reward(player_id)
    # Mark the matching vote ID claimed after granting the reward.
Node.js Express webhook receiver
import express from 'express'
import crypto from 'crypto'

const app = express()
app.use(express.raw({ type: 'application/json' }))

app.post('/webhooks/palworld-vote', (req, res) => {
  const signature = req.header('X-Webhook-Signature')
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex')

  if (!signature || !crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'))) {
    return res.sendStatus(401)
  }

  const event = JSON.parse(req.body.toString('utf8'))
  if (event.type === 'vote') grantVoteReward(event.playerId)
  return res.sendStatus(204)
})

Rate Limits

  • Standard API limit: 100 requests per minute per IP address.
  • Vote cooldown: 12 hours per user, per server.