PlayStation SDK Setup Guide (C++)
---
sidebar_label: PlayStation (C++) sidebar_category: Gaming SDK Guides
PlayStation SDK Setup Guide (C++)
This guide covers integrating Gaming Intelligence into PlayStation 4 and PlayStation 5 games using C++ and the PS NET HTTP library. It demonstrates HTTP transport, event serialization, HMAC request signing, session lifecycle handling, and offline queuing patterns that are compatible with PlayStation certification requirements.
Developer Program Note: PlayStation SDK access requires a registered Sony developer account and acceptance of the associated NDA. PS4 (Orbis) and PS5 (Prospero) SDK function names, parameter types, and initialization sequences differ between versions. The patterns shown below are representative of the general integration shape. Consult your Sony DevNet documentation for exact API signatures.
NDA Notice: Certain API names and header constants shown in this guide (such as
sceNetHttp*,sceUserService*,SCE_NET_HTTP_*) are covered by the PlayStation SDK NDA. This guide presents the general integration pattern. Consult your Sony DevNet documentation for exact API names, constant values, and parameter types for your target SDK version.
Prerequisites
Before you begin, have the following ready:
| Requirement | Where to Find It |
|---|---|
| SDK Key | Linkzly Console → Gaming → [Your Game] → Settings → SDK Configuration → SDK Key |
| Organization ID | Linkzly Console → Organization Settings |
| Game ID | Linkzly Console → Gaming → [Your Game] → Settings → General Settings |
| Signing Secret | Linkzly Console → Gaming → [Your Game] → Settings → SDK Configuration → Signing Secret |
| PlayStation SDK | Sony DevNet developer portal (requires NDA-restricted license) |
| mbedTLS | Recommended for HMAC-SHA256; portable and suitable for console memory constraints |
| rapidjson | Lightweight JSON serializer recommended for console environments |
Your SDK key is masked by default in the console. Use the eye icon to reveal it or the copy button to copy it directly.
Platform Value
All events sent from PlayStation builds must include "platform": "playstation" in the event payload.
Architecture Overview
PlayStation certification prohibits blocking the main game thread on network I/O. The recommended integration pattern separates event production from event delivery:
- Game thread: Enqueues events into a thread-safe in-memory buffer.
- Network thread: Dequeues events in batches, serializes them, signs the request, and POSTs to the ingestion endpoint.
- Offline queue: If the request fails or the console is offline, events are retained in memory and flushed on the next successful connection.
Step 1: Player Identity
PlayStation does not expose a single persistent numeric user ID across all contexts. Use one of the following approaches for the player_id field:
- PSN Account ID: Available through the PS4/PS5 user service. Query
sceUserServiceGetLoginUserIdList()and map to a PSN account ID via the NP service. This is the most stable cross-device identifier. - Locally generated UUID: If PSN account lookups are not feasible (for example, during early boot before sign-in), generate a UUID on first launch and persist it to a save file or trophies/user storage. Use the same UUID for all subsequent sessions on that console account.
// Pseudocode — refer to your SDK for exact user service API signatures
SceUserServiceUserId userId;
sceUserServiceGetInitialUser(&userId);
// Map userId to a stable string for Linkzly
char playerIdBuf[64];
snprintf(playerIdBuf, sizeof(playerIdBuf), "psn_%u", (unsigned int)userId);
std::string playerId = playerIdBuf;
Step 2: HTTP Transport via PS NET
The PS NET library provides HTTPS request primitives. The pattern below reflects the general PS4/PS5 HTTP client flow. Consult your SDK documentation for exact function signatures and flag constant values.
Initialization
Initialize the PS NET HTTP module once at startup, before making any requests:
// Call once during your network module initialization (e.g., before first event send)
// Parameters and flag constants vary between PS4 and PS5 SDK versions
int ret = sceNetHttpInit(SCE_NET_HTTP_POOL_SIZE_DEFAULT);
if (ret < 0) {
// Handle initialization failure — log the error code
}
Sending an Event Batch
#include <string>
#include <cstring>
void SendEventBatch(
const std::string& sdkKey,
const std::string& orgId,
const std::string& gameId,
const std::string& jsonBody,
const std::string& timestamp,
const std::string& nonce,
const std::string& signature)
{
// 1. Create HTTP template (connection pool handle)
// Parameters: name, HTTP version, pool size, flags
int templateId = sceNetHttpCreateTemplate(
"linkzly-sdk",
SCE_NET_HTTP_VERSION_1_1,
SCE_NET_HTTP_POOLSIZE_DEFAULT);
if (templateId < 0) return;
// 2. Create connection to the ingestion host (TLS on port 443)
int connId = sceNetHttpCreateConnection(
templateId,
"gaming.linkzly.com",
443,
SCE_NET_HTTP_ENABLE);
if (connId < 0) {
sceNetHttpDeleteTemplate(templateId);
return;
}
// 3. Create the POST request
int reqId = sceNetHttpCreateRequest(
connId,
SCE_NET_HTTP_METHOD_POST,
"/api/v1/gaming/events",
SCE_NET_HTTP_VERSION_1_1);
if (reqId < 0) {
sceNetHttpDeleteConnection(connId);
sceNetHttpDeleteTemplate(templateId);
return;
}
// 4. Set authentication and signing headers
std::string authHeader = "Bearer " + sdkKey;
sceNetHttpAddRequestHeader(reqId, "Authorization",
authHeader.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "X-Organization-ID",
orgId.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "X-Game-ID",
gameId.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "Content-Type",
"application/json", SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "X-Timestamp",
timestamp.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "X-Nonce",
nonce.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
sceNetHttpAddRequestHeader(reqId, "X-Signature-256",
signature.c_str(), SCE_NET_HTTP_HEADER_OVERWRITE);
// 5. Attach body and send
const uint8_t* bodyData = reinterpret_cast<const uint8_t*>(jsonBody.c_str());
uint32_t bodySize = static_cast<uint32_t>(jsonBody.size());
int sendRet = sceNetHttpSendRequest(reqId, bodyData, bodySize);
if (sendRet < 0) {
// Network error — queue for retry
sceNetHttpDeleteRequest(reqId);
sceNetHttpDeleteConnection(connId);
sceNetHttpDeleteTemplate(templateId);
return;
}
// 6. Read response status code
int statusCode = 0;
sceNetHttpGetStatusCode(reqId, &statusCode);
if (statusCode == 202) {
// Batch accepted — optionally read body for batch_id and trace_id
} else {
// Log statusCode for debugging; re-queue events if transient (5xx)
}
// 7. Release all handles
sceNetHttpDeleteRequest(reqId);
sceNetHttpDeleteConnection(connId);
sceNetHttpDeleteTemplate(templateId);
}
Threading requirement: PlayStation certification requires all HTTP calls to run off the main game thread. Use
sceKernelCreateThread()or your engine's threading abstraction to dispatch network work to a dedicated network thread.
Step 3: JSON Serialization
Use a lightweight JSON library suited to console memory constraints. rapidjson is recommended due to its in-place parsing, minimal allocations, and lack of STL dependencies.
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
using namespace rapidjson;
std::string BuildEventPayload(
const std::string& playerId,
const std::string& sessionId,
const std::string& eventType,
const std::string& gameVersion)
{
Document doc;
doc.SetObject();
auto& alloc = doc.GetAllocator();
Value eventsArray(kArrayType);
Value eventObj(kObjectType);
eventObj.AddMember("event_id",
Value(GenerateUUID().c_str(), alloc), alloc);
eventObj.AddMember("event_type",
Value(eventType.c_str(), alloc), alloc);
eventObj.AddMember("timestamp",
Value(GetCurrentISOTimestamp().c_str(), alloc), alloc);
eventObj.AddMember("platform",
Value("playstation", alloc), alloc);
eventObj.AddMember("player_id",
Value(playerId.c_str(), alloc), alloc);
eventObj.AddMember("session_id",
Value(sessionId.c_str(), alloc), alloc);
eventObj.AddMember("sdk_version",
Value("1.0.0", alloc), alloc);
if (!gameVersion.empty())
eventObj.AddMember("game_version",
Value(gameVersion.c_str(), alloc), alloc);
eventsArray.PushBack(eventObj, alloc);
doc.AddMember("events", eventsArray, alloc);
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
doc.Accept(writer);
return std::string(buffer.GetString(), buffer.GetSize());
}
Step 4: HMAC Signing
HMAC signing is enabled by default for all new games. To disable it during development: Console → Gaming → [Your Game] → Settings → Security Settings → HMAC Signing Required → Off.
mbedTLS is recommended on PlayStation. It is lightweight, portable, and avoids the heavier footprint of OpenSSL on constrained console environments.
HMAC-SHA256 with mbedTLS
#include "mbedtls/md.h"
#include <sstream>
#include <iomanip>
// Signing string format: "{timestamp}.{nonce}.{body}"
std::string ComputeHmacSha256(
const std::string& signingSecret,
const std::string& timestamp,
const std::string& nonce,
const std::string& body)
{
std::string signingString = timestamp + "." + nonce + "." + body;
unsigned char output[32];
mbedtls_md_context_t ctx;
const mbedtls_md_info_t* mdInfo =
mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mdInfo, 1 /* use HMAC mode */);
mbedtls_md_hmac_starts(&ctx,
reinterpret_cast<const unsigned char*>(signingSecret.c_str()),
signingSecret.size());
mbedtls_md_hmac_update(&ctx,
reinterpret_cast<const unsigned char*>(signingString.c_str()),
signingString.size());
mbedtls_md_hmac_finish(&ctx, output);
mbedtls_md_free(&ctx);
std::ostringstream hex;
for (int i = 0; i < 32; i++)
hex << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(output[i]);
return hex.str();
}
mbedTLS configuration: Ensure
MBEDTLS_MD_CandMBEDTLS_SHA256_Care enabled in your mbedTLS configuration header. Some PS SDK distributions ship a minimal mbedTLS build that requires enabling these explicitly.
HMAC headers:
| Header | Value |
|---|---|
X-Timestamp |
Unix timestamp in milliseconds (e.g., 1714000000000) |
X-Nonce |
UUID v4, unique per request |
X-Signature-256 |
HMAC-SHA256 hex of {timestamp}.{nonce}.{body} |
Replay window: 300 seconds. Requests with timestamps older than 5 minutes from the server clock are rejected.
Step 5: Session Management
PlayStation handles application lifecycle differently from PC. The OS can suspend and resume a title without terminating it. Sessions must account for this:
static std::string currentSessionId;
// On initial game load or after player account selection
void OnGameStart(const std::string& playerId)
{
currentSessionId = GenerateUUID();
std::string timestamp = GetTimestampMs();
std::string nonce = GenerateUUID();
std::string body = BuildEventPayload(
playerId, currentSessionId, "session_start", "1.0.0");
std::string signature = ComputeHmacSha256(
SIGNING_SECRET, timestamp, nonce, body);
// Dispatch on network thread
DispatchEventAsync(body, timestamp, nonce, signature);
}
// On system suspend event (SCE_SYSTEM_SERVICE_EVENT_ON_STANDBY or equivalent)
// Do NOT send session_end — the game may resume
void OnSuspend()
{
// Flush in-memory event queue to avoid losing queued events
FlushEventQueueAsync();
}
// On resume — continue the same session, do not start a new one
void OnResume()
{
// Re-establish network if needed; no new session_start required
}
// On actual game exit (player quits from system menu)
void OnGameEnd(const std::string& playerId)
{
std::string timestamp = GetTimestampMs();
std::string nonce = GenerateUUID();
std::string body = BuildEventPayload(
playerId, currentSessionId, "session_end", "1.0.0");
std::string signature = ComputeHmacSha256(
SIGNING_SECRET, timestamp, nonce, body);
DispatchEventAsync(body, timestamp, nonce, signature);
}
Certification note: Do not perform blocking network calls inside suspend/resume callbacks. PlayStation certification requires suspend handling to complete within a strict time budget. Flush pending events asynchronously before acknowledging suspend.
Step 6: Offline Event Queuing
PlayStation users may start games in areas with intermittent connectivity (particularly in rest mode or during downloads). Queue events locally and flush when the network is available.
#include <deque>
#include <string>
#include <mutex>
class LinkzlyEventQueue
{
public:
static const size_t MAX_QUEUE_SIZE = 500;
void Enqueue(const std::string& jsonPayload)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_queue.size() >= MAX_QUEUE_SIZE)
m_queue.pop_front(); // Drop oldest events when full
m_queue.push_back(jsonPayload);
}
void Flush(LinkzlyClient& client)
{
if (!IsNetworkAvailable()) return;
std::lock_guard<std::mutex> lock(m_mutex);
while (!m_queue.empty())
{
client.SendRawPayload(m_queue.front());
m_queue.pop_front();
}
}
bool IsEmpty() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.empty();
}
private:
std::deque<std::string> m_queue;
mutable std::mutex m_mutex;
bool IsNetworkAvailable()
{
// Use sceNetCtl or sceNetCtlGetState to check network reachability
// Return true when HTTPS to gaming.linkzly.com is reachable
int state = 0;
sceNetCtlGetState(&state);
return (state == SCE_NET_CTL_STATE_IP_OBT);
}
};
Step 7: Full Integration Class
// LinkzlySDK.h
#pragma once
#include <string>
#include <deque>
#include <mutex>
class LinkzlySDK
{
public:
// Call once at startup with your credentials
void Init(const std::string& sdkKey,
const std::string& signingSecret,
const std::string& orgId,
const std::string& gameId);
// Set player identity after authentication
void Identify(const std::string& playerId);
// Track a single event; session_id managed internally
void Track(const std::string& eventType,
const std::string& gameVersion = "");
// Manually flush queued events (call on resume and before shutdown)
void Flush();
private:
std::string m_SdkKey;
std::string m_SigningSecret;
std::string m_OrgId;
std::string m_GameId;
std::string m_PlayerId;
std::string m_SessionId;
std::deque<std::string> m_pendingEvents;
std::mutex m_queueMutex;
void DispatchEventAsync(const std::string& jsonBody);
bool IsNetworkAvailable();
};
// Usage in game initialization
LinkzlySDK sdk;
sdk.Init(
"// YOUR_SDK_KEY",
"// YOUR_SIGNING_SECRET",
"// YOUR_ORG_ID",
"// YOUR_GAME_ID"
);
// After player sign-in
sdk.Identify("psn_1234567890");
sdk.Track("session_start", "2.0.0");
// In-game event
sdk.Track("level_complete");
// On shutdown
sdk.Track("session_end");
sdk.Flush();
Helper Utilities
#include <chrono>
#include <sstream>
#include <iomanip>
#include <random>
#include <ctime>
std::string GetTimestampMs()
{
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
return std::to_string(ms);
}
std::string GetCurrentISOTimestamp()
{
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm* utc = std::gmtime(&t);
std::ostringstream oss;
oss << std::put_time(utc, "%Y-%m-%dT%H:%M:%SZ");
return oss.str();
}
std::string GenerateUUID()
{
static std::random_device rd;
static std::mt19937_64 gen(rd());
static std::uniform_int_distribution<uint64_t> dis;
uint64_t a = dis(gen), b = dis(gen);
a = (a & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
b = (b & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
std::ostringstream ss;
ss << std::hex << std::setfill('0')
<< std::setw(8) << static_cast<uint32_t>(a >> 32) << "-"
<< std::setw(4) << static_cast<uint16_t>(a >> 16) << "-"
<< std::setw(4) << static_cast<uint16_t>(a) << "-"
<< std::setw(4) << static_cast<uint16_t>(b >> 48) << "-"
<< std::setw(12) << (b & 0x0000FFFFFFFFFFFFULL);
return ss.str();
}
Batch Response
A successful batch submission returns HTTP 202 Accepted with a JSON body:
{
"success": true,
"batch_id": "batch_01jk...",
"events_received": 10,
"events_valid": 10,
"events_dropped": 0,
"trace_id": "trace_abc...",
"server_timestamp": "2025-04-15T12:00:00Z"
}
Certification Checklist
Before submitting for PlayStation certification, verify the following:
| Check | Requirement |
|---|---|
| No blocking network calls on main thread | All HTTP must run on a dedicated network thread |
| Suspend/resume handled correctly | Do not block suspend acknowledgment with network I/O |
| Network reachability checked before sending | Use sceNetCtl or equivalent before each send attempt |
| Memory usage within console limits | Prefer rapidjson over heavier JSON libraries |
| TLS certificate verification enabled | Do not disable SSL certificate validation |
| SDK Key stored securely | Do not embed credentials in plaintext in release builds |
| Offline queue bounded | Cap in-memory queue to prevent unbounded memory growth |
Troubleshooting
| Issue | Resolution |
|---|---|
| 401 Unauthorized | Verify SDK Key, Org ID, and Game ID are correct and have no extra whitespace |
| 400 Bad Request | Ensure all required event fields are present: event_id, event_type, timestamp, platform, player_id, session_id |
| HMAC mismatch | Verify system clock is NTP-synced; replay window is 300 seconds. Confirm signing string is {timestamp}.{nonce}.{body} |
| Network call blocks certification | Move all HTTP to a dedicated thread; never call from main or render thread |
| Events lost on suspend | Flush event queue asynchronously in OnSuspend(); do not wait for network confirmation |
| mbedTLS compilation errors | Ensure MBEDTLS_MD_C and MBEDTLS_SHA256_C are enabled in your mbedTLS config header |
sceNetHttp* returns negative |
Log the return code; most PS NET errors are negative SCE_NET_HTTP_ERROR_* constants defined in <libhttp.h> |
Next Steps
Was this helpful?
Help us improve our documentation