What is Roochip Lite?

Roochip Lite is a standalone B2B credit issuing platform. Rooverse does not own any payment processing, credit storage, or ledger logic. Instead, Rooverse calls Roochip’s REST API and receives credit events via signed webhooks.

Think of Roochip Lite as the credit bank. Rooverse is the app that uses it. Users never interact with Roochip directly — they see only the Rooverse UI.

Closed-loop policy ROO credits are non-monetary, non-transferable, and non-withdrawable. They have no real-world cash value and may only be used within the Rooverse app. Never represent ROO as having monetary value.

The ROO Currency

Precision
2 decimal places
Stored as NUMERIC(12,2). 1 ROO = 1.00
Smallest unit
0.01 ROO
Used for micro-rewards like likes
Max wallet balance
9,999,999,999.99
NUMERIC(12,2) ceiling
Type
Closed-loop
Cannot be withdrawn or transferred
Peg
1 ROO = $1 USD
Stable credit — fixed 1:1 value with US Dollar

One unified wallet

Purchased ROO and earned ROO are the same currency in one wallet. There is no distinction between “bought credits” and “reward credits” in the balance — only the ledger history records how credits were obtained.

How to display ROO in the UI

ValueDisplayNotes
5.005 ROOHide trailing decimal for whole numbers
5.505.5 ROOShow 1 decimal if meaningful
120.01120.01 ROOFull precision in transaction history
0.01+0.01 ROOPrefix with + for reward toasts

System Architecture

How all the components connect:

[ iOS App ] [ Android App ] [ Web Browser ] | | | v v v [ Rooverse Frontend / Mobile App ] <-- users see this only | v [ Rooverse Backend Server ] <-- makes all Roochip API calls | X-API-Key | | | receives webhooks v v [ Roochip Lite REST API ] | v [ Supabase (PostgreSQL) ] <-- wallets, ledger, payments | v [ Stripe ] / [ Apple IAP ] / [ Google IAP ] Payment processors (Roochip-owned, not Rooverse)

Key principle

When a user pays, money goes to Roochip Lite’s payment accounts. Roochip is the merchant of record. Rooverse has zero payment compliance obligations for ROO purchases.

Rollout Phases

● Phase 1 — Active Now
Promo Codes
  • Buy button returns “coming soon”
  • Users request a free 5 ROO promo code
  • Admin generates & shares the code
  • User enters code in app to redeem
  • One request + one redemption per user, ever
○ Phase 2 — Payment Gateway Live
Real Purchases
  • Stripe checkout via WebView (web & tablet)
  • Apple IAP (iOS)
  • Google IAP (Android)
  • Package selection UI in app
  • Checkout returns real checkout_url
Migration is seamless for Rooverse When Phase 2 goes live, POST /v1/checkout switches from returning 503 coming_soon to a real checkout_url. No other changes to the Rooverse integration are required.

User Wallet & Balance

Every Rooverse user has exactly one ROO wallet, managed entirely by Roochip Lite. The wallet is created automatically on the first API call for that user — Rooverse does not need to explicitly register users.

Where the balance lives

LocationWhat it isWhen to use
Roochip Lite databaseAuthoritative balanceAlways fetch live before any deduction
users.roochip_balance (Rooverse DB)Cached balance — updated by webhookDisplay in UI, leaderboards, low-latency reads

When to refresh the displayed balance

Wallet screen — what to show

UI elementData sourceNotes
Current balanceGET /v1/users/:id/balancebalanceFetch live on screen open
Transaction historyGET /v1/users/:id/transactionsPaginated, 20 per page
“Buy More” buttonPhase 1: shows coming soon + code request. Phase 2: opens checkout.
Transaction type labelentries[].typepurchase / reward / deduction / adjustment
Amount signentries[].credits_deltaPositive = added, negative = deducted

Earning ROO — Reward System

Users earn ROO by performing actions inside Rooverse. Rooverse owns all reward rules — Roochip has no knowledge of triggers, caps, or amounts. Rooverse applies its own logic before calling the reward API.

Suggested reward values

TriggerROOreason stringCap recommendation
Like a post0.01likeMax 50 rewarded likes/day per user
Follow a user0.05followMax 10 rewarded follows/day per user
Daily login streak0.10daily_loginOnce per calendar day per user
Create a post0.02post_createdMax 5/day
Referral signup5.00referral_bonusOnce per referred user
7-day streak milestone1.00streak_7dayOnce per streak completion

These are suggestions only. Rooverse controls the amounts, triggers, and caps entirely.

Rooverse must enforce all reward limits before calling the API Roochip does not know how many likes a user has received today. If Rooverse calls POST /v1/users/:id/reward, Roochip issues the credits unconditionally. Build daily caps, cooldowns, and deduplication on the Rooverse side first.

Anti-abuse implementation pattern

// Rooverse backend pseudocode
async function handleLikeEvent(userId, postId) {
  // 1. Rooverse rules check
  const todayLikes = await db.countRewardsToday(userId, 'like');
  if (todayLikes >= 50) return;
  const alreadyRewarded = await db.rewardExists(userId, postId, 'like');
  if (alreadyRewarded) return;

  // 2. Only now call Roochip
  await roochip.post(`/v1/users/${userId}/reward`, {
    credits: 0.01,
    reason: 'like',
    reference_id: postId
  });
}

Spending ROO — Paid Features

When a user activates a paid feature, the Rooverse backend calls the deduct API. Credits are deducted atomically — PostgreSQL row-level locking prevents concurrent overdrafts.

Example feature costs

FeatureSuggested costreason string
AI-generated content10.00 ROOai_generation
Premium filter / effect5.00 ROOpremium_filter
Boost post visibility20.00 ROOpost_boost
Profile badge / cosmetic50.00 ROOprofile_badge
Unlock exclusive content15.00 ROOcontent_unlock
Always deduct before running the feature Call POST /v1/users/:id/deduct first and verify a 200 response before executing the feature. If you run the feature first and deduction fails, the user receives it for free. On 402 Insufficient credits — block the feature and show the upsell prompt.

Deduct-then-run pattern

async function runAIGeneration(userId, jobId) {
  const COST = 10.00;

  // 1. Deduct first (atomic, row-locked)
  const result = await roochip.post(`/v1/users/${userId}/deduct`, {
    credits: COST,
    reason: 'ai_generation',
    reference_id: jobId
  });

  if (result.status === 402) {
    return { error: 'insufficient_credits', required: COST };
  }

  // 2. Credits confirmed deducted - now run the feature
  await runAIJob(jobId);

  // 3. Update Rooverse UI cache
  await db.users.update({ roochip_balance: result.data.new_balance }, userId);
}

Buying ROO — Purchase Flow

Users top up their ROO wallet by purchasing credit packages. Packages are defined and priced by Roochip Lite. Rooverse fetches and displays them — it never sets prices or creates packages.

Available packages (current seed data)

NameROO CreditsPriceValue per ROO
Starter Pack10 ROO$10.00$1.00 per ROO
Popular Pack25 ROO$25.00$1.00 per ROO
Premium Pack50 ROO$50.00$1.00 per ROO

Phase 1 — “Coming soon” response

Until Stripe is configured, POST /v1/checkout returns:

HTTP 503
{
  "status": "coming_soon",
  "message": "Payment system coming soon! In the meantime, request a free 5-credit code.",
  "request_code_endpoint": "POST /v1/codes/request"
}

Display this message and show a “Request a free code” button immediately below it.

Phase 2 — Real checkout (future)

POST /v1/checkout will return a checkout_url. Web: open in in-app WebView. Mobile: use native IAP (see Payment Methods).

Payment Methods

MethodPlatformWho processesTypical feeStatus
StripeWeb / WebViewRoochip Lite~1.5–2.9% + 30pPending keys
Apple IAPiOS nativeApple → Roochip Lite15–30%Pending keys
Google IAPAndroid nativeGoogle → Roochip Lite15–30%Pending service account
Promo CodeAnyAdmin-issued, freeNoneLive now
Rooverse has no responsibility for payment fees All processor fees are deducted before money reaches Roochip Lite. This is entirely invisible to Rooverse — you see only the credits.issued webhook.

Why mobile must use native IAP

Apple and Google require that in-app purchases of digital goods go through their own payment systems — not Stripe. The Rooverse mobile app sends the receipt / purchase token to the Rooverse backend, which forwards it to Roochip for server-side verification and credit issuance.

Mobile IAP call (from Rooverse backend)

// iOS: after successful StoreKit purchase
const response = await fetch('https://nevis-credit-system-test.vercel.app/iap/apple', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.ROOCHIP_API_KEY },
  body: JSON.stringify({
    external_user_id: userId,
    receipt_data: appleReceiptBase64,  // from StoreKit
    package_id: selectedPackage.id,
    tenant_id: TENANT_ID
  })
});
// Roochip verifies with Apple, issues credits, fires webhook

Ownership Boundaries

ConcernRoochip LiteRooverse
Stripe account & keysRoochip ownsNone
Apple IAP verificationRoochip ownsNone
Google IAP verificationRoochip ownsNone
Credit wallet & ledgerRoochip ownsNone
Credit packages & pricingRoochip ownsNone
User identity & authNoneRooverse owns, sends user IDs
Balance display in UINoneRooverse reads via API
Reward rules & capsNoneRooverse decides, calls reward API
Deducting for featuresNoneRooverse calls deduct API
Promo code generationAdmin generates via Roochip APINone
Promo code redemption UINoneRooverse shows input field

Authentication

All Rooverse → Roochip calls are server-to-server only. Never call the Roochip API from frontend JavaScript, React Native bundles, or a WebView.

X-API-Key: rc_test_rooverse_key_123   <!-- test key; replace with production key for live -->
Keep this key secret Store only as a server-side environment variable. Never commit to version control. If exposed, rotate it immediately.

Environment Variables (Rooverse backend)

# Provided by Roochip Lite during onboarding
ROOCHIP_API_KEY=rc_test_rooverse_key_123   # replace with production key for live
ROOCHIP_BASE_URL=https://nevis-credit-system-test.vercel.app
ROOCHIP_WEBHOOK_SECRET=roochip_provided_secret

Phase 1: Promo Code Flow

This is the active buy flow right now Payment gateway is not yet live. Users can request one free 5 ROO code each. One request and one redemption per user, ever.
User taps “Buy Roochips” in app
Rooverse backend calls POST /v1/checkout. Returns 503 status: coming_soon.
App shows “Payment coming soon” message
Display the message from the API response. Show a “Request Free Code” button below it.
User taps “Request Free Code”
Rooverse backend calls POST /v1/codes/request with the user’s ID. On success, show confirmation. On 409, show “You have already requested a code.”
POST /v1/codes/request
{ "external_user_id": "rooverse-user-123" }
Admin reviews and generates code
Admin calls GET /v1/admin/codes/requests?status=pending, then POST /v1/admin/codes/generate. System returns a code like ROO-A3F29C1B. Admin shares with user via email, DM, etc.
User enters code in app
Show a code input field. Rooverse backend calls POST /v1/codes/redeem. On success, 5 ROO added instantly.
POST /v1/codes/redeem
{ "external_user_id": "rooverse-user-123", "code": "ROO-A3F29C1B" }
Update UI + receive webhook
Use new_balance from the redeem response to update balance display immediately. Roochip also fires a credits.issued webhook confirming the issuance.

Promo Code UI — What to Build

Wallet / Buy screen

UI ElementBehaviour
Current balanceFetch GET /balance on screen open. Display as “120 ROO”
“Buy More ROO” buttonCalls POST /v1/checkout. On 503 shows Phase 1 message
“Payment coming soon” bannerShown when checkout returns 503
“Request a free code” buttonCalls POST /v1/codes/request. Hide after first request.
“Enter promo code” inputAlways visible or toggled. Submits to POST /v1/codes/redeem
Transaction historyCalls GET /transactions. Load more on scroll.

After requesting a code

On success → “Your request has been received. We’ll send you a code soon.”
On 409“You’ve already submitted a request. Check your messages for your code.”

Recommended: track request state in Rooverse DB Add roochip_code_requested BOOLEAN DEFAULT FALSE to the users table. Set to TRUE when the user taps “Request Code”. Use this to show “Code requested — check your messages” instead of the button on subsequent visits.

Promo Code Endpoints

POST /v1/codes/request User requests a promo code (once per user)
// Request
{ "external_user_id": "rooverse-user-123" }

// 201 Success
{ "message": "Request received. An admin will generate your code shortly.", "status": "pending" }

// 409 Already requested
{ "error": "You have already requested a promo code. Each account can only request once." }
POST /v1/codes/redeem Redeem a code — issues 5 ROO instantly
// Request
{ "external_user_id": "rooverse-user-123", "code": "ROO-A3F29C1B" }

// 200 Success
{ "message": "5 ROO credits added to your account.", "credits_issued": 5, "new_balance": 5 }
StatusMeaningShow in UI
404Code not found“This code is invalid.”
403Code for a different user“This code was not issued to this account.”
409Already used“This code has already been redeemed.”
GET /v1/admin/codes/requests?status=pending Admin: list code requests
{
  "requests": [
    { "id": "uuid", "external_user_id": "rooverse-user-123", "status": "pending", "created_at": "..." }
  ],
  "count": 1
}

Filter by status: pending | fulfilled | rejected

POST /v1/admin/codes/generate Admin: generate a one-use code for a user
// Request
{ "external_user_id": "rooverse-user-123" }

// 201 Success
{
  "code": "ROO-A3F29C1B",
  "external_user_id": "rooverse-user-123",
  "credits": 5,
  "message": "Share this code with rooverse-user-123. It is single-use."
}

Get Available Packages

GET /v1/packages Returns active packages for this tenant
{
  "packages": [
    { "id": "uuid", "name": "Starter Pack",  "credits_to_issue": 10, "price_amount": 1000, "currency": "usd" },
    { "id": "uuid", "name": "Popular Pack",  "credits_to_issue": 25, "price_amount": 2500, "currency": "usd" },
    { "id": "uuid", "name": "Premium Pack",  "credits_to_issue": 50, "price_amount": 5000, "currency": "usd" }
  ]
}
Note: price_amount is in cents (USD). Divide by 100: 1000 → $10.00. Pricing model: 1 Roochip Lite = $1 USD (stable 1:1 peg).

Get User Balance

GET /v1/users/{external_user_id}/balance Live authoritative balance
{ "external_user_id": "rooverse-user-123", "balance": 115.50, "updated_at": "2026-03-30T10:00:00Z" }

Get Transaction History

GET /v1/users/{external_user_id}/transactions?page=1&limit=20 Paginated ledger entries
{
  "external_user_id": "rooverse-user-123",
  "total": 3, "page": 1, "limit": 20,
  "entries": [
    { "type": "purchase",  "credits_delta":  120.00, "balance_before":   0, "balance_after": 120.00, "created_at": "..." },
    { "type": "reward",    "credits_delta":    0.01, "balance_before": 120, "balance_after": 120.01, "created_at": "..." },
    { "type": "deduction", "credits_delta":  -10.00, "balance_before": 120.01, "balance_after": 110.01, "created_at": "..." }
  ]
}

Deduct Credits

Server-side only — never call from frontend or mobile app
POST /v1/users/{external_user_id}/deduct Deduct ROO for a paid feature (atomic)
// Request
{ "credits": 10.00, "reason": "ai_generation", "reference_id": "rooverse-job-abc123" }

// 200 Success
{ "external_user_id": "rooverse-user-123", "credits_deducted": 10.00, "new_balance": 110.00 }

// 402 Insufficient credits
{ "error": "Insufficient credits" }

Issue a Reward

POST /v1/users/{external_user_id}/reward Issue earned ROO after Rooverse rule checks
// Request
{ "credits": 0.01, "reason": "like", "reference_id": "post-uuid-abc" }

// 200 Success
{ "external_user_id": "rooverse-user-123", "credits_rewarded": 0.01, "new_balance": 120.01 }

Inbound Webhooks — Roochip → Rooverse

After any credit issuance (purchase, IAP, promo, reward), Roochip POSTs to your webhook URL:

POST https://rooverse.app/webhooks/roochip

Webhook payload

{
  "event": "credits.issued",
  "external_user_id": "rooverse-user-123",
  "credits_issued": 120.00,
  "new_balance": 320.00,
  "payment_source": "stripe",
  "timestamp": "2026-03-30T10:00:00Z"
}

payment_source: stripe | apple_iap | google_iap | promo_code

What Rooverse must do on receipt

Verify the HMAC-SHA256 signature
Reject with 401 if invalid. See verification code below.
Deduplicate the event
Roochip retries on failure. Your handler must be idempotent. Log to roochip_events with a unique key.
Update cached balance
Set users.roochip_balance = new_balance for external_user_id.
Return 200 OK immediately
Do not do heavy work inside the handler. Acknowledge first, process async if needed.

Webhook Signature Verification

const crypto = require('crypto');

function verifyRoochipWebhook(req, secret) {
  const signature = req.headers['x-roochip-signature'];
  if (!signature) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(req.body))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

app.post('/webhooks/roochip', express.json(), async (req, res) => {
  if (!verifyRoochipWebhook(req, process.env.ROOCHIP_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  const { event, external_user_id, credits_issued, new_balance, payment_source } = req.body;
  if (event === 'credits.issued') {
    await db.users.update({ roochip_balance: new_balance }, { where: { id: external_user_id } });
    await db.roochipEvents.create({ event, external_user_id, credits_issued, new_balance, payment_source });
  }
  res.sendStatus(200);
});

Rooverse Database Changes Required

These are the only database changes Rooverse needs. All wallet logic lives in Roochip’s database.

1. Cached balance column (required)

ALTER TABLE users
  ADD COLUMN IF NOT EXISTS roochip_balance NUMERIC(12,2) NOT NULL DEFAULT 0;

CREATE INDEX IF NOT EXISTS idx_users_roochip_balance ON users(roochip_balance);
Cache only — never treat as authoritative Updated by the incoming webhook. Always fetch live from GET /balance before any deduction to prevent races.

2. Event log table (required)

CREATE TABLE IF NOT EXISTS roochip_events (
  id               UUID          PRIMARY KEY DEFAULT gen_random_uuid(),
  external_user_id TEXT          NOT NULL,
  event            TEXT          NOT NULL,                 -- 'credits.issued'
  credits_issued   NUMERIC(12,2),
  new_balance      NUMERIC(12,2),
  payment_source   TEXT,                                   -- stripe|apple_iap|google_iap|promo_code
  raw_payload      JSONB,                                  -- full webhook body
  received_at      TIMESTAMPTZ   NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_roochip_events_user ON roochip_events(external_user_id);
CREATE INDEX IF NOT EXISTS idx_roochip_events_time ON roochip_events(received_at DESC);

3. Code request flag (recommended)

ALTER TABLE users
  ADD COLUMN IF NOT EXISTS roochip_code_requested BOOLEAN NOT NULL DEFAULT FALSE;

Set to TRUE when the user taps “Request Code”. Hides the button and shows “Code requested — check your messages” on subsequent visits.

Summary

ObjectTypePurposeRequired?
users.roochip_balanceColumnCached balance for fast UI displayRequired
roochip_eventsTableWebhook log and audit trailRequired
users.roochip_code_requestedColumnHide “Request Code” button after first requestRecommended

Buy Flow — Future (Phase 2)

User taps “Buy Roochips”
Rooverse calls GET /v1/packages and displays package cards with prices.
User selects a package
Rooverse backend calls POST /v1/checkout → receives checkout_url.
Open WebView or native IAP
Web: open checkout_url in in-app WebView — user never leaves the app. iOS: StoreKit. Android: Play Billing.
User completes payment
Payment handled entirely by processor. Rooverse has no involvement.
Redirect back to app
WebView closes via success_url. Show: “Payment received — adding credits…” Do NOT credit wallet here.
Roochip fires webhook
Roochip verifies with processor, issues credits, fires credits.issued to Rooverse.
Rooverse updates balance
Update roochip_balance cache and log event. UI refreshes with new balance.
Never credit on redirect The success_url redirect can be navigated to directly — it is not proof of payment. Credits must only be issued via the signed Roochip webhook.

Spend Credits Flow

User triggers a paid feature
e.g. taps “Generate AI image” — costs 10 ROO.
Fast-fail check (optional)
If users.roochip_balance < 10 in Rooverse DB → show “Not enough ROO” without an API call.
Call POST deduct
Atomic deduction. Will return 402 even if cached balance looks sufficient (race condition protection).
On 200 — run the feature
Update roochip_balance with new_balance from response. Execute the feature.
On 402 — block and upsell
Show “You need X more ROO” and a “Buy Roochips” button. Do not run the feature.

Reward Flow

User performs a rewarded action
Like, follow, daily login, referral, content creation, etc.
Rooverse checks its own rules
Daily cap not exceeded? Not already rewarded for this exact event? All checks pass before calling Roochip.
Call POST reward
Include reference_id (your internal event ID) for traceability. Roochip issues credits unconditionally.
Update UI
Update cached balance with new_balance. Animate a “+0.01 ROO” toast or notification.

Error Codes

HTTPMeaningAction
200Success
201Created
400Bad request — missing or invalid fieldsFix the payload
401Invalid or missing API keyCheck X-API-Key header and env var
402Insufficient creditsShow upsell UI, block the feature
403Tenant inactive or code issued to different userContact Roochip support
404Package, user, or code not foundVerify IDs are correct
409Duplicate request or code already usedDo not retry — already processed
503 coming_soonPayment gateway not yet liveShow coming soon message, offer code request
500Roochip server errorRetry with exponential backoff

What Rooverse Does NOT Need to Build

All of the following is handled entirely by Roochip Lite