Complete guide for integrating ViewFi video rewards with your Roblox experience.
The ViewFi platform enables Roblox developers to distribute UGC item rewards through video ad campaigns. Players watch a video, receive a unique 8-character redemption code, then redeem it in your Roblox game via deeplink or manual code entry.
1. Player visits ViewFi widget 2. Watches video + claims reward
(video-rewards.com/widget/X) (fingerprint-only auth, no login)
| |
v v
3. 8-char code generated 4. Player deeplinked to Roblox
(24h expiry window) (code embedded in LaunchData)
| |
v v
5. Your game server calls 6. Grant UGC item, then call
/v1/codes/verify /v1/codes/confirm
Ready-to-use scripts for your Roblox game. The best way to get these is from the Roblox Integration card in your campaign dashboard, which pre-fills your API Key ID.
Place this in ServerScriptService as a ModuleScript named ViewFiRewards:
-- ViewFi Reward Verification Service for Roblox
--
-- IMPORTANT: Store API_SECRET in a ServerScript or secure storage
-- Never expose the secret in client-side code!
--
-- Status Codes:
-- "available" - Code is valid and ready for minting (has 24h expiry window)
-- "redeemed" - Already redeemed in-game
-- "pending" - User hasn't completed ViewFi flow yet
-- "expired" - 24h reservation window expired, code released
-- "invalid" - Code not found or doesn't belong to campaign
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local ViewFiRewards = {}
-- Configuration (UPDATE THESE VALUES)
local API_KEY_ID = "your-api-key-id" -- From Roblox Integration card
local API_SECRET = "YOUR_API_SECRET_HERE" -- Keep secure!
local BASE_URL = "https://video-rewards.com"
-- Retry configuration for network resilience
local MAX_RETRIES = 3
local RETRY_DELAY_SECONDS = 1 -- Base delay, doubles each retry (exponential backoff)
-- Status code messages for player display
local STATUS_MESSAGES = {
available = "Code verified! Granting your reward...",
redeemed = "This code has already been redeemed",
pending = "Please complete the video on ViewFi first",
expired = "This code has expired (24h limit). Please get a new code from ViewFi.",
invalid = "Invalid code. Please check and try again.",
error = "Something went wrong. Please try again later.",
unauthorized = "API authentication failed. Contact game developer.",
server_error = "ViewFi server error. Please try again later."
}
-- Internal helper: Make HTTP request with retry logic
local function makeRequestWithRetry(requestOptions)
local lastError = nil
for attempt = 1, MAX_RETRIES do
local success, response = pcall(function()
return HttpService:RequestAsync(requestOptions)
end)
if success then
return true, response
else
lastError = response
warn("[ViewFi] Request attempt " .. attempt .. " failed:", response)
if attempt < MAX_RETRIES then
local delay = RETRY_DELAY_SECONDS * (2 ^ (attempt - 1))
task.wait(delay)
end
end
end
return false, lastError
end
-- Internal helper: Parse HTTP response with status code handling
local function parseResponse(response)
if response.StatusCode == 401 then
warn("[ViewFi] Authentication failed - check API credentials")
return { success = false, status = "unauthorized", error = STATUS_MESSAGES.unauthorized }
end
if response.StatusCode == 429 then
warn("[ViewFi] Rate limited")
return { success = false, status = "rate_limited", error = "Too many requests. Please wait." }
end
if response.StatusCode >= 500 then
warn("[ViewFi] Server error:", response.StatusCode)
return { success = false, status = "server_error", error = STATUS_MESSAGES.server_error }
end
if response.StatusCode ~= 200 then
warn("[ViewFi] Unexpected status code:", response.StatusCode)
return { success = false, status = "error", error = "Unexpected error (code " .. response.StatusCode .. ")" }
end
local parseSuccess, data = pcall(function()
return HttpService:JSONDecode(response.Body)
end)
if not parseSuccess then
warn("[ViewFi] Failed to parse response:", data)
return { success = false, status = "error", error = "Invalid server response" }
end
data.message = STATUS_MESSAGES[data.status] or STATUS_MESSAGES.error
return data
end
-- Verify a reward code before minting
function ViewFiRewards.VerifyCode(rewardCode, player, viewfiIdentifier)
local url = BASE_URL .. "/api/roblox/v1/codes/verify"
local requestBody = HttpService:JSONEncode({
rewardCode = rewardCode,
robloxUserId = tostring(player.UserId),
robloxUsername = player.Name,
viewfiIdentifier = viewfiIdentifier
})
local success, response = makeRequestWithRetry({
Url = url,
Method = "POST",
Headers = {
["Content-Type"] = "application/json",
["X-ViewFi-API-Key-Id"] = API_KEY_ID,
["X-ViewFi-API-Secret"] = API_SECRET
},
Body = requestBody
})
if not success then
warn("[ViewFi] All retry attempts failed:", response)
return { success = false, status = "error", error = "Network error after " .. MAX_RETRIES .. " retries" }
end
return parseResponse(response)
end
-- Confirm a redemption after successfully minting the UGC item
-- This is idempotent - calling multiple times for same code is safe
function ViewFiRewards.ConfirmRedemption(rewardCode, player, metadata)
local url = BASE_URL .. "/api/roblox/v1/codes/confirm"
local requestBody = HttpService:JSONEncode({
rewardCode = rewardCode,
robloxUserId = tostring(player.UserId),
robloxUsername = player.Name,
metadata = metadata or {}
})
local success, response = makeRequestWithRetry({
Url = url,
Method = "POST",
Headers = {
["Content-Type"] = "application/json",
["X-ViewFi-API-Key-Id"] = API_KEY_ID,
["X-ViewFi-API-Secret"] = API_SECRET
},
Body = requestBody
})
if not success then
warn("[ViewFi] Confirm request failed after retries:", response)
return { success = false, error = "Network error after " .. MAX_RETRIES .. " retries" }
end
return parseResponse(response)
end
-- Helper: Get remaining time string from verify response
function ViewFiRewards.GetRemainingTimeString(verifyResult)
if verifyResult.remainingTime then
local hours = verifyResult.remainingTime.hours or 0
local minutes = verifyResult.remainingTime.minutes or 0
if hours > 0 then
return string.format("%dh %dm remaining", hours, minutes)
else
return string.format("%dm remaining", minutes)
end
end
return nil
end
return ViewFiRewards
Place this as a Script in ServerScriptService alongside the module above:
-- ViewFi Reward Handler
-- Handles deeplink codes (LaunchData) and manual code entry
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ViewFiRewards = require(script.Parent.ViewFiRewards)
-- Create RemoteEvent for client communication
local RewardRemote = Instance.new("RemoteEvent")
RewardRemote.Name = "RewardRemote"
RewardRemote.Parent = ReplicatedStorage
-- Your UGC item granting function (IMPLEMENT THIS)
local function grantUGCItem(player)
-- Replace with your actual reward logic
-- Example: grant a UGC item, badge, or in-game currency
print("[ViewFi] Granting UGC item to", player.Name)
return true
end
-- Full reward claim flow
local function claimReward(player, rewardCode)
-- Step 1: Verify the code
local verifyResult = ViewFiRewards.VerifyCode(rewardCode, player)
if verifyResult.status == "available" then
-- Step 2: Grant the reward
local success = grantUGCItem(player)
if success then
-- Step 3: Confirm with ViewFi
ViewFiRewards.ConfirmRedemption(rewardCode, player, {
itemGranted = true,
timestamp = os.time()
})
RewardRemote:FireClient(player, {
success = true,
message = "Reward claimed successfully!"
})
else
RewardRemote:FireClient(player, {
success = false,
message = "Failed to grant reward. Try again."
})
end
else
RewardRemote:FireClient(player, {
success = false,
message = verifyResult.message or "Unable to verify code"
})
end
end
-- Check for reward code in deeplink LaunchData when player joins
local function handlePlayerJoin(player)
task.spawn(function()
task.wait(2) -- Wait for player to fully load
local joinData = player:GetJoinData()
if joinData and joinData.LaunchData then
local success, launchParams = pcall(function()
return HttpService:JSONDecode(joinData.LaunchData)
end)
if success and launchParams and launchParams.reward then
print("[ViewFi] Found reward code in LaunchData:", launchParams.reward)
claimReward(player, launchParams.reward)
end
end
end)
end
-- Handle manual code entry from client
RewardRemote.OnServerEvent:Connect(function(player, action, data)
if action == "CLAIM_REWARD" and typeof(data) == "string" and #data > 0 then
claimReward(player, data)
end
end)
-- Connect player join event
Players.PlayerAdded:Connect(handlePlayerJoin)
-- Handle players already in game (for Studio testing)
for _, player in pairs(Players:GetPlayers()) do
handlePlayerJoin(player)
end
print("[ViewFi] Reward handler initialized")
Place this LocalScript in StarterPlayerScripts for the code entry UI:
-- ViewFi Client Reward Handler
-- Provides UI for manual code entry and reward notifications
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local SoundService = game:GetService("SoundService")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local RewardRemote = ReplicatedStorage:WaitForChild("RewardRemote", 30)
if not RewardRemote then
warn("RewardRemote not found - reward system disabled")
return
end
-- Create reward notification popup
local function createRewardUI()
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "RewardSystemUI"
screenGui.ResetOnSpawn = false
screenGui.Parent = playerGui
local mainFrame = Instance.new("Frame")
mainFrame.Name = "RewardFrame"
mainFrame.Size = UDim2.new(0, 400, 0, 200)
mainFrame.Position = UDim2.new(0.5, -200, 0.5, -100)
mainFrame.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
mainFrame.BorderSizePixel = 0
mainFrame.Visible = false
mainFrame.Parent = screenGui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 12)
corner.Parent = mainFrame
local titleLabel = Instance.new("TextLabel")
titleLabel.Name = "TitleLabel"
titleLabel.Size = UDim2.new(1, 0, 0, 50)
titleLabel.BackgroundTransparency = 1
titleLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
titleLabel.TextScaled = true
titleLabel.Font = Enum.Font.SourceSansBold
titleLabel.Parent = mainFrame
local messageLabel = Instance.new("TextLabel")
messageLabel.Name = "MessageLabel"
messageLabel.Size = UDim2.new(1, -40, 0, 80)
messageLabel.Position = UDim2.new(0, 20, 0, 60)
messageLabel.BackgroundTransparency = 1
messageLabel.TextColor3 = Color3.fromRGB(200, 200, 200)
messageLabel.TextScaled = true
messageLabel.TextWrapped = true
messageLabel.Font = Enum.Font.SourceSans
messageLabel.Parent = mainFrame
local closeButton = Instance.new("TextButton")
closeButton.Size = UDim2.new(0, 100, 0, 30)
closeButton.Position = UDim2.new(0.5, -50, 1, -45)
closeButton.BackgroundColor3 = Color3.fromRGB(76, 175, 80)
closeButton.BorderSizePixel = 0
closeButton.Text = "Awesome!"
closeButton.TextColor3 = Color3.fromRGB(255, 255, 255)
closeButton.TextScaled = true
closeButton.Font = Enum.Font.SourceSansBold
closeButton.Parent = mainFrame
local closeCorner = Instance.new("UICorner")
closeCorner.CornerRadius = UDim.new(0, 6)
closeCorner.Parent = closeButton
closeButton.Activated:Connect(function()
local fadeOut = TweenService:Create(mainFrame, TweenInfo.new(0.3), { BackgroundTransparency = 1 })
fadeOut:Play()
fadeOut.Completed:Connect(function()
mainFrame.Visible = false
mainFrame.BackgroundTransparency = 0
end)
end)
return mainFrame, titleLabel, messageLabel
end
local rewardFrame, titleLabel, messageLabel = createRewardUI()
-- Handle reward responses from server
RewardRemote.OnClientEvent:Connect(function(responseData)
if not responseData then return end
if responseData.success then
titleLabel.Text = "Reward Claimed!"
titleLabel.TextColor3 = Color3.fromRGB(76, 175, 80)
else
titleLabel.Text = "Claim Failed"
titleLabel.TextColor3 = Color3.fromRGB(244, 67, 54)
end
messageLabel.Text = responseData.message or "Unknown response"
rewardFrame.Visible = true
-- Auto-close after 5 seconds
task.delay(5, function()
if rewardFrame.Visible then
rewardFrame.Visible = false
end
end)
end)
print("[ViewFi] Client reward handler loaded")
When a player claims a reward on ViewFi, they are automatically deeplinked into your Roblox game with the reward code embedded in LaunchData:
roblox://experiences/start?placeId=YOUR_PLACE_ID&launchData={"reward":"ABCD1234"}
The server script reads LaunchData on player join and processes the code automatically. Players can also manually enter codes via the client UI.
Questions? Check our troubleshooting guide or reach out to our team.