Skip to content
ReinoAds

IntegrationSDK Beta

Reward Verification (S2S)

Verify rewarded ads with a signed server-to-server notification.

If you set an S2S callback URL on a rewarded unit, ReinoAds sends a signed POST request to your server when the user completes the video. Grant valuable rewards based on this notification rather than the client-side onReward.

Setup

  • Edit the rewarded unit in the dashboard and enter a callback URL starting with https.
  • Copy the unit's secret key from the same screen and store it on your server. The key can be rotated at any time; the old key stops working immediately.
  • Pass the user identity in the SDK; it is returned as userId and customData in the notification.
<ReinoAdsProvider
  appKey={APP_KEYS}
  options={{ user: { id: "player-42", customData: "level=7" } }}
>
  <Root />
</ReinoAdsProvider>

ReinoAds.setUser({ id: "player-42", customData: "level=8" });

userId can be up to 128 and customData up to 512 characters.

Request

POST /reinoads/reward HTTP/1.1
Content-Type: application/json
User-Agent: ReinoAds-Callbacks/1.0
X-Reino-Timestamp: 1793260800
X-Reino-Signature: sha256=5f2b...c41e

{
  "transactionId": "9b4921e9111c7778897003c4ed39696e",
  "adUnitId": "rau_...",
  "rewardName": "coin",
  "rewardAmount": 10,
  "userId": "player-42",
  "customData": "level=7",
  "timestamp": 1793260795
}
FieldDescription
transactionIdUnique per impression. Store it to avoid granting the same reward twice.
adUnitIdThe rewarded unit ID.
rewardName, rewardAmountThe reward defined for the unit in the dashboard.
userId, customDataUser data passed in the SDK; empty if not provided.
timestampTime of the reward event (Unix seconds).

Verifying the signature

X-Reino-Signature is an HMAC-SHA256 computed with the unit secret: sha256=hex(HMAC(secret, X-Reino-Timestamp + "." + raw body)). Do not re-serialize the JSON; compute it over the raw bytes you received.

import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

const app = express();

app.post("/reinoads/reward", express.raw({ type: "application/json" }), async (req, res) => {
  const timestamp = req.header("X-Reino-Timestamp") ?? "";
  const signature = req.header("X-Reino-Signature") ?? "";
  const body = req.body.toString("utf8");

  const expected = "sha256=" + createHmac("sha256", process.env.REINOADS_UNIT_SECRET!)
    .update(`${timestamp}.${body}`)
    .digest("hex");
  const valid = signature.length === expected.length
    && timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  if (!valid || !fresh) {
    return res.status(401).end();
  }

  const event = JSON.parse(body);
  await grantRewardOnce(event.transactionId, event.userId, event.rewardName, event.rewardAmount);
  res.status(200).end();
});

Responses and retries

  • A 2xx response counts as success.
  • 4xx responses other than 408 and 429 are treated as permanent rejections and not retried.
  • Other errors and timeouts (5 s) are retried with exponential backoff, 6 attempts in total, so deduplicate by transactionId.
  • No notification is sent for impressions in test mode.