Reachability Verification
Prove players can actually connect: the none, TCP connect, and UDP echo challenge modes, with the exact DSCV1 wire spec.
A heartbeat only proves your game server has outbound connectivity. It says nothing about whether players can connect inbound: a server behind a closed firewall port still sends heartbeats. Verification closes that gap by probing the advertised endpoint before listing the server.
Pick a mode per Discovery app: open the app page and set Verification in Settings. Changes reach the Discovery service within about a minute.
The three modes
No verification (
none): no probe. Any server that heartbeats is listed. Fastest to integrate, but a server behind a closed firewall port will still appear in the list.TCP connect (
tcp): Discovery opens and immediately drops a TCP connection to the advertised endpoint. No code needed on your side; any listening TCP socket passes. Only use this if your game server actually listens on TCP.UDP echo challenge (
udp-echo): Discovery sends a small UDP challenge; your server must echo the exact bytes back. Proves both reachability and that the heartbeat came from the machine that owns the endpoint. Needs a responder of roughly ten lines (below).
Open-registration apps always use the UDP echo challenge
On an app with open registration (see Discovery Overview), Verification is locked to UDP echo challenge and cannot be changed. The platform rejects any other combination, and switching an existing app to open registration locks the setting automatically.
The reason: on an open app the heartbeat token ships inside the game binary and is public, so token secrecy cannot protect the list. Proving that a sender actually controls the endpoint it advertises is what keeps fabricated entries away from your players, so it is not optional in this mode. Every client-hosted server must run the echo responder below.
Servers hosted on PingCore
Verification applies to heartbeat integrations, meaning servers you host elsewhere. Servers hosted on PingCore through a fleet are platform-vouched: the platform allocated their endpoints and health-checks their processes, so Discovery lists them under every verification mode without probing them. The live server table on the app page marks them as hosted instead of showing a verification result.
How probing behaves
Probes run asynchronously and never block heartbeat ingest.
A server is probed on its first heartbeat, then re-probed every 5 minutes.
The probe timeout is 3 seconds.
While a server is not verified (and the mode is not
none), it keeps accepting heartbeats but is hidden from the public list.The heartbeat response is your diagnostic surface:
verifiedreportspending,verified, orunverified, andlastProbeErrorcarries the reason for the last failure. Log it on your servers so misconfigurations show up in your own logs.
TCP connect checklist
TCP mode needs no responder code. Most failures come from the network path:
Your game server must already be listening on the advertised port over TCP. If it is UDP-only, use the UDP echo challenge instead.
The advertised
ip:portmust be reachable from the public internet, not just your LAN. Port-forward it on the router or open it in the cloud security group.Host firewalls block inbound by default. Allow the port in Windows Defender Firewall or ufw/iptables.
If you sit behind NAT and advertise a different external port than the one your process binds, send the externally reachable port in the heartbeat.
UDP echo challenge: the DSCV1 wire spec
The format is a fixed wire contract shipped in studio code, so implement it exactly:
Discovery sends a single UDP datagram to the advertised endpoint:
ip:queryPortwhen your heartbeat includes aqueryPort, otherwiseip:port.The datagram is exactly 21 bytes: the 5 ASCII bytes
DSCV1followed by a 16-byte random nonce.Your server sends the exact same 21 bytes straight back to the source address, from the same socket that received them.
A reply carrying a different nonce is rejected, so an off-path attacker cannot forge verification for someone else's endpoint.
No reply within the probe timeout means unverified: the server keeps heartbeating but stays hidden from the public list until a later probe succeeds.
import dgram from 'node:dgram';
const MAGIC = Buffer.from('DSCV1', 'ascii');
export function runEchoResponder(port) {
const socket = dgram.createSocket('udp4');
socket.on('message', (message, remote) => {
// 5 magic bytes + 16 byte nonce, echoed back verbatim.
if (message.length !== 21) return;
if (!message.subarray(0, MAGIC.length).equals(MAGIC)) return;
socket.send(message, remote.port, remote.address);
});
socket.bind(port);
}// Minimal DSCV1 responder. Bind a socket to the port you advertise
// (or to queryPort if you send one in the heartbeat).
using System.Net.Sockets;
static readonly byte[] Magic = System.Text.Encoding.ASCII.GetBytes("DSCV1");
async Task RunEchoResponderAsync(int port, CancellationToken ct)
{
using var socket = new UdpClient(port);
while (!ct.IsCancellationRequested)
{
var received = await socket.ReceiveAsync(ct);
var data = received.Buffer;
// 5 magic bytes + 16 byte nonce
if (data.Length != 21) continue;
var valid = true;
for (var i = 0; i < Magic.Length; i++)
{
if (data[i] != Magic[i]) { valid = false; break; }
}
if (!valid) continue;
// Echo the exact bytes back to the sender, same socket.
await socket.SendAsync(data, data.Length, received.RemoteEndPoint);
}
}// Minimal DSCV1 responder (POSIX sockets; Winsock is the same shape).
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
void RunEchoResponder(int port)
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
unsigned char buffer[64];
sockaddr_in from{};
socklen_t fromLen = sizeof(from);
for (;;)
{
ssize_t n = recvfrom(sock, buffer, sizeof(buffer), 0,
reinterpret_cast<sockaddr*>(&from), &fromLen);
// 5 magic bytes + 16 byte nonce, echoed back verbatim.
if (n == 21 && std::memcmp(buffer, "DSCV1", 5) == 0)
sendto(sock, buffer, n, 0, reinterpret_cast<sockaddr*>(&from), fromLen);
}
}Many games can hook the responder into the socket they already use for queries: check the first five bytes, echo if they match, otherwise fall through to your normal query handling.
What verification does not prove
Verification proves the endpoint answers Discovery. It does not prove every player's network route works. A server can be reachable from Discovery's vantage point and still be blocked for some players by their ISP, a regional firewall, or a broken route. Verification filters out dead listings; it does not guarantee that every individual player can connect.