ViewFi Docs ← Documentation Home

layout: default title: Roblox Integration permalink: /roblox-integration/

Roblox UGC Integration

Complete guide for integrating ViewFi video rewards with your Roblox experience.

Overview

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.

Quick Links

How It Works

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

Scripts

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.

Server Script (ServerScriptService)

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

Reward Handler (ServerScriptService)

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")

Client Script (StarterPlayerScripts)

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")

Getting Started

  1. Create a Roblox UGC campaign in the ViewFi dashboard
  2. Enter your Place ID (15 digits) in the Rewards tab
  3. Generate API credentials from the Roblox Integration card
  4. Copy the Lua Script (pre-configured with your API Key ID)
  5. Add scripts to your Roblox experience
  6. Test the flow end-to-end, then go live

Deeplink Flow

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.

Support

Questions? Check our troubleshooting guide or reach out to our team.

Documentation

Getting Started Campaign Management Claude / MCP Connector Advertising & DSP CTV Integration Roblox Integration Fortnite Integration API Reference Developer Resources Troubleshooting