Configure a listing callback
In User Control Panel → Edit listing, save a public HTTP(S) Callback URL and its productive per-listing secret. Private, loopback, link-local, reserved and userinfo URLs are rejected.
Send a stable player identifier
Use id in your vote link. postback and incentive remain compatible aliases. The first non-empty value is forwarded as userid.
https://metin2-servers.com/index.php?a=in&u=TOPLIST_USERNAME&id=VOTER_IDOptional custom value: add &callback=campaign-spring. It is forwarded unchanged as custom.
Validate and reward once
Every callback contains matching query and POST fields. Validate the secret before doing anything and reward only voted=1.
secretProductive listing secret.voted1 for a new vote, 0 for a duplicate.resetUnix timestamp for the next vote reset.useridPlayer ID from the vote link.PHP receiver example
Keep the secret outside source control and store a unique reward key per player and reset time.
<?php
$secret = (string) ($_POST['secret'] ?? '');
$expected = getenv('TOPLIST_CALLBACK_SECRET');
if (!is_string($expected) || $expected === '' || !hash_equals($expected, $secret)) { http_response_code(403); exit; }
$voted = (string) ($_POST['voted'] ?? '0');
$userId = (string) ($_POST['userid'] ?? '');
$reset = filter_var($_POST['reset'] ?? null, FILTER_VALIDATE_INT);
if ($voted === '1' && $userId !== '' && $reset !== false) {
// Store a unique userId + reset reward record, then grant the reward.
}
http_response_code(200); echo 'ok';Send a public test callback
This form always sends secret=TEST; it never reads a listing secret. It returns only Callback accepted or Callback failed.