Skip to main content
Session-Based Realtime Ready PID Scoped
Verification sessions let you start a PID-based verification flow and read the result without needing webhooks.

How it works

1

Create a session

Send a POST request to /api/v1/verify/session with your pid and your internal clientId.
2

Redirect the user

Send the user to the returned verifyUrl so they can complete the Dock verification flow.
3

Wait for completion

Consume the result using either long-polling or Server-Sent Events (SSE).


Long-poll

Use GET /api/v1/verify/session/:sid?wait=25 when you want a simple request-based waiting flow.

SSE stream

Use GET /api/v1/verify/session/:sid/stream when you want realtime updates over a single connection.

Create a session

Request body

pid
string
required
Your Dock Public Identifier. This determines which PID dataset the verification session belongs to.
clientId
string
required
Your internal user identifier. Dock stores this on the session so you can map verification completion back to your own account or user record.
guildId
string
Optional guild identifier used when your verification flow also needs guild context.
curl --request POST \
  --url "https://api.docksys.xyz/api/v1/verify/session" \
  --header "Authorization: Bearer YOUR_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "pid": "pid-yourapp",
    "clientId": "user_123",
    "guildId": "optional"
  }'
{
  "status": 200,
  "data": {
    "sid": "vsn_A1b2C3d4E5f6G7h8J9k0L1",
    "pid": "pid-yourapp",
    "clientId": "user_123",
    "expiresAt": "2026-02-28T19:40:12.108Z",
    "reusedExisting": false,
    "verifyUrl": "https://api.docksys.xyz/v1/api/verify/discord?pid=pid-yourapp&sid=vsn_A1b2C3d4E5f6G7h8J9k0L1"
  },
  "timestamp": "2026-02-28T19:35:12.108Z",
  "version": "1.3.0",
  "verifyRequestsRemaining": 1999
}

Response fields

status
number
required
HTTP status code for the request.
data.sid
string
required
Unique verification session ID.
data.pid
string
required
PID associated with the session.
data.clientId
string
required
Your application’s internal user identifier for this session.
data.expiresAt
string
required
ISO 8601 timestamp indicating when the session expires.
data.reusedExisting
boolean
required
Whether Dock reused an already-active session instead of creating a new one.
data.verifyUrl
string
required
Verification URL you should redirect the user to.
verifyRequestsRemaining
number
Remaining requests in the verification-session bucket.
Each session stores your required clientId, which makes it easy to map a completed verification back to the correct user in your own system.

Read session completion

Use SSE when you want one open connection that receives updates as soon as the session changes state.
Node.js SSE client
const API_BASE = "https://api.docksys.xyz";
const API_KEY = process.env.DOCK_API_KEY;

async function createSession(pid, clientId) {
  const response = await fetch(`${API_BASE}/api/v1/verify/session`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ pid, clientId }),
  });

  if (!response.ok) {
    throw new Error(`createSession failed (${response.status})`);
  }

  const payload = await response.json();
  return payload.data;
}

async function streamSession(sid) {
  const streamResponse = await fetch(
    `${API_BASE}/api/v1/verify/session/${sid}/stream`,
    {
      headers: {
        Authorization: `Bearer ${API_KEY}`,
      },
    },
  );

  if (!streamResponse.ok) {
    throw new Error(`stream failed (${streamResponse.status})`);
  }

  const reader = streamResponse.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });

    let eventBoundary;
    while ((eventBoundary = buffer.indexOf("\n\n")) !== -1) {
      const rawEvent = buffer.slice(0, eventBoundary);
      buffer = buffer.slice(eventBoundary + 2);

      const eventLine = rawEvent
        .split("\n")
        .find((line) => line.startsWith("event:"));
      const dataLine = rawEvent
        .split("\n")
        .find((line) => line.startsWith("data:"));

      if (!eventLine || !dataLine) continue;

      const eventName = eventLine.replace("event:", "").trim();
      const data = JSON.parse(dataLine.replace("data:", "").trim());

      if (eventName === "complete") {
        return data;
      }

      if (eventName === "expired") {
        throw new Error("Verification session expired before completion");
      }
    }
  }

  throw new Error("Stream closed before completion");
}

Session states and result shape

Completed sessions include sid, pid, and a populated result object.
Pending sessions include sid, pid, status, and result: null.
Expired sessions include sid, pid, status: "expired", and result remains null if completion never happened.

Completed result fields

sid
string
required
Unique verification session ID.
pid
string
required
PID the session belongs to.
result.discordId
string
required
Verified Discord user ID.
result.robloxId
string
required
Verified Roblox user ID.
Indicates whether this Discord user was previously linked to a different Roblox account in the PID dataset.
result.previousRobloxId
string | null
Previous Roblox ID for the same Discord user when linkChanged is true.

Pending or expired fields

sid
string
required
Unique verification session ID.
pid
string
required
PID the session belongs to.
status
string
required
Current session state, such as pending or expired.
result
null
required
null until verification completes.
linkChanged is true when the Discord user in this PID dataset was previously linked to a different Roblox account.

Realtime deployment note

Dock uses Redis pub/sub to wake long-poll and SSE listeners across multiple API instances.
If Redis is unavailable, Dock falls back to local in-process events. This still works on single-instance deployments, but it does not provide cross-instance realtime wakeups.