In-game Rewards

Receiving In-game Rewards

Any websites can provide rewards into supporting games that are published by Noctua. To make your game support delivery of the rewards, this document will guide you on the implementation.

Note

In-game rewards are delivered by Noctua SDK Backend to your game server, and not directly to the game client.

The delivery from the game server to the game client is out of the scope of this document.

Callback for Reward Items Delivery

To ensure players receive their reward items, you need to set up a callback webhook. This webhook will be notified by the Noctua SDK Backend when a reward item needs to be delivered.

  • Implement an API endpoint in your game server to receive reward notifications
  • Register this webhook URL in the Noctua Developer Dashboard. You can contact us if you need help with this.
  • When a reward is granted, your webhook will receive a POST request with reward details

Callback Idempotency

Please ensure that your callback handler is idempotent. This means that when it is called multiple times with the same exact request payload, it should produce an effect only once—for example, ensuring that an item is delivered only once per reward.

Request Headers

The webhook request will include a custom header for security validation:

X-CALLBACK-TOKEN: <your_secret_token>

Validating the X-CALLBACK-TOKEN

To ensure the callback is genuine and comes from the Noctua SDK Backend:

  1. Retrieve the X-CALLBACK-TOKEN from the request headers.
  2. Compare this token with the secret token provided in your Noctua Developer Dashboard.
  3. If the tokens match, process the webhook. If not, reject the request.

Here's a simple example of how to validate the token

<?php

function validate_callback_token($headers) {
    $expected_token = 'your_secret_token_from_noctua_dashboard';

    // Check if the X-CALLBACK-TOKEN header exists
    if (isset($headers['X-CALLBACK-TOKEN'])) {
        $received_token = $headers['X-CALLBACK-TOKEN'];

        if ($received_token === $expected_token) {
            // Token is valid, process the webhook
            return true;
        }
    }

    // Invalid token or missing header, reject the request
    return false;
}

// Usage example:
$headers = getallheaders(); // This function gets all HTTP headers
if (validate_callback_token($headers)) {
    // Process the webhook
    process_webhook(json_decode(file_get_contents('php://input'), true));
} else {
    // Reject the request
    http_response_code(403);
    echo 'Invalid token';
}
?>

Request Body

The webhook will receive a JSON payload with the following structure:

{
    "data": {
		"reward_id":        12345678,
		"player_id":        485934,
		"ingame_role_id":   "gmu8950",
		"ingame_server_id": "srv8976",
		"reward_time":      "2024-01-01T12:00:00Z",
		"title":            "Birthday Reward",
		"body":             "This is our gift to you.",
		"type":             "ingame_item",
		"items":            [
            {
                "id":         "ingame_reward_id_123",
                "product_id": "foobar.pack1",
                "image_url":  "https://upload.wikimedia.org/wikipedia/en/6/63/Feels_good_man.jpg",
                "quantity":   1
            }
        ],
    },
    "signed_data": "eyJhbGciOiJFUzI1NiIsIm..."
}

The data field contains raw data while the signed_data is the JWS (JSON Web Signature) version of the same data. It is recommended to load from signed_data instead of from data.

JWKS

The JWKS used to verify signed_data is the same as the one used to verify user access token.

Here are some examples on how to verify and load the data from signed_data.

<?php

use Jose\Component\Core\JWK;
use Jose\Component\Core\JWKSet;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Serializer\CompactSerializer;

// Fetch JWKS from the server
$jwksJson = file_get_contents('https://sdk-api-v2.noctuaprojects.com/api/v1/auth/jwks');
$jwks = JWKSet::createFromJson($jwksJson);

$signedData = 'SIGNED_DATA_STRING';

// Parse the signed data
$serializer = new CompactSerializer();
$jws = $serializer->unserialize($signedData);

// Get the key ID from the signed data header
$headers = $jws->getSignature(0)->getProtectedHeader();
$kid = $headers['kid'] ?? null;

if ($kid === null) {
    throw new Exception('No "kid" found in signed data header');
}

// Find the corresponding key in the JWKS
$jwk = $jwks->get($kid);

if ($jwk === null) {
    throw new Exception('No matching key found in JWKS');
}

// Verify the signature
$algorithm = new ES256();
$algorithmManager = new \Jose\Component\Core\AlgorithmManager([$algorithm]);
$jwsVerifier = new JWSVerifier($algorithmManager);

if (!$jwsVerifier->verifyWithKey($jws, $jwk, 0)) {
    throw new Exception('Signed data signature verification failed');
}

// If we reach here, the signed data is valid
$payload = json_decode($jws->getPayload(), true);

// Now $payload contains the decoded signed data
Previous
Providing in-game rewards