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
}| Field | Description |
|---|---|
| transactionId | Unique per impression. Store it to avoid granting the same reward twice. |
| adUnitId | The rewarded unit ID. |
| rewardName, rewardAmount | The reward defined for the unit in the dashboard. |
| userId, customData | User data passed in the SDK; empty if not provided. |
| timestamp | Time 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.
