ScamVerify™
Tutorials

Build a Fraud Investigation Dashboard in Retool

Create an internal tool for ops teams to investigate phone numbers and URLs using the ScamVerify™ API.

This tutorial walks you through building a fraud investigation dashboard in Retool. Your ops team will be able to paste a phone number or URL, see the full ScamVerify™ risk assessment, and take action (block an account, flag for review, or mark as safe) all from one screen.

Prerequisites

What You Will Build

An internal dashboard with:

  1. A search input for phone numbers or URLs
  2. A risk score gauge with color-coded verdict
  3. A detailed signals breakdown table
  4. An AI-generated explanation panel
  5. Action buttons for case management

Create the API resource

In Retool, go to Resources and click Create New > REST API.

Configure the resource:

  • Name: ScamVerify API
  • Base URL: https://scamverify.ai/api/v1
  • Headers:
    • Authorization: Bearer sv_live_your_key_here
    • Content-Type: application/json

Click Save. This resource is now available to all queries in your Retool apps.

Store your API key as a Retool environment variable for better security. Go to Settings > Environment Variables, add SCAMVERIFY_API_KEY, then reference it as {{ env.SCAMVERIFY_API_KEY }} in the header.

Create the Retool app

Go to Apps and click Create New > App. Name it "Fraud Investigation Dashboard".

You will see the Retool canvas. The app will have two main sections:

  1. Top section: Search input and submit button
  2. Bottom section: Results displayed in cards, table, and gauge

Add the search input

Drag these components onto the canvas:

Text Input (for phone/URL entry):

  • Label: "Phone Number or URL"
  • Placeholder: "Enter a phone number or URL to investigate"
  • Name: searchInput

Select (for lookup type):

  • Label: "Type"
  • Values: phone, url
  • Labels: "Phone Number", "URL"
  • Default value: phone
  • Name: lookupType

Button (to trigger the search):

  • Label: "Investigate"
  • Name: investigateButton
  • Color: Primary

Arrange them in a row across the top of the canvas.

Create the API queries

Create two queries in the bottom panel:

Query 1: Phone Lookup

  • Name: phoneLookup
  • Resource: ScamVerify API
  • Method: POST
  • Path: /phone/lookup
  • Body:
{
  "phone_number": "{{ searchInput.value }}"
}
  • Run automatically: No (triggered by button only)

Query 2: URL Lookup

  • Name: urlLookup
  • Resource: ScamVerify API
  • Method: POST
  • Path: /url/lookup
  • Body:
{
  "url": "{{ searchInput.value }}"
}
  • Run automatically: No

Now configure the Investigate button's click handler:

// investigateButton onClick handler
if (lookupType.value === 'phone') {
  phoneLookup.trigger();
} else {
  urlLookup.trigger();
}

Add a helper to unify results

Create a JavaScript Transformer to normalize the response from whichever query ran:

  • Name: currentResult
  • Code:
const phoneData = phoneLookup.data;
const urlData = urlLookup.data;

// Use whichever query was last triggered
if (lookupType.value === 'phone' && phoneData) {
  return {
    type: 'phone',
    query: searchInput.value,
    risk_score: phoneData.risk_score,
    verdict: phoneData.verdict,
    confidence: phoneData.confidence,
    explanation: phoneData.explanation,
    signals: phoneData.signals || {},
    cached: phoneData.cached,
  };
} else if (urlData) {
  return {
    type: 'url',
    query: searchInput.value,
    risk_score: urlData.risk_score,
    verdict: urlData.verdict,
    confidence: urlData.confidence,
    explanation: urlData.explanation,
    signals: urlData.signals || {},
    cached: urlData.cached,
  };
}
return null;

Build the results display

Add these components below the search section:

Statistic Component (Risk Score Gauge):

  • Value: {{ currentResult.value?.risk_score ?? '-' }}
  • Label: "Risk Score"
  • Suffix: "/ 100"
  • Color: Use a ternary based on score:
{{ currentResult.value?.risk_score >= 70 ? 'red' :
   currentResult.value?.risk_score >= 30 ? 'orange' : 'green' }}

Statistic Component (Verdict):

  • Value: {{ (currentResult.value?.verdict || '-').replace('_', ' ').toUpperCase() }}
  • Label: "Verdict"

Statistic Component (Confidence):

  • Value: {{ currentResult.value ? Math.round(currentResult.value.confidence * 100) + '%' : '-' }}
  • Label: "Confidence"

Arrange these three statistics in a row.

Text Component (AI Explanation):

  • Value: {{ currentResult.value?.explanation || 'Run a lookup to see results.' }}
  • Style: Background color #f9fafb, padding 16px, border radius 8px

Add the signals table

Drag a Table component below the explanation text.

  • Name: signalsTable
  • Data source:
{{
  currentResult.value?.signals
    ? Object.entries(currentResult.value.signals).map(([source, data]) => ({
        source: source.replace(/_/g, ' ').toUpperCase(),
        status: typeof data === 'object' ? (data.found ? 'Found' : 'Clean') : String(data),
        details: typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data),
      }))
    : []
}}

Configure three columns:

ColumnKeyWidth
Sourcesource25%
Statusstatus15%
Detailsdetails60%

Set the Status column to use a Tag type with conditional colors:

  • "Found" = red
  • "Clean" = green

Add action buttons

Below the signals table, add a row of buttons for case management:

Button: "Mark as Safe"

  • Color: Green
  • onClick: Your internal API to clear the flag, or a Retool query to update your database

Button: "Flag for Review"

  • Color: Orange
  • onClick: Create a ticket in your ticketing system or update a status field

Button: "Block Account"

  • Color: Red
  • onClick: Call your internal API to block the account associated with this phone/URL
  • Add a confirmation dialog: "Are you sure you want to block this account?"

Example onClick handler for "Block Account" (assuming you have an internal API resource):

// Trigger a query that calls your internal API
blockAccountQuery.trigger({
  additionalScope: {
    phoneOrUrl: searchInput.value,
    riskScore: currentResult.value?.risk_score,
    verdict: currentResult.value?.verdict,
  }
});

These action buttons connect to your own backend systems. The ScamVerify™ API provides the risk data, and your internal tools handle the enforcement actions.

Add a lookup history table

Add a second Table at the bottom of the page to show recent lookups. Create a Retool Database table called investigation_log with columns: id, query, type, risk_score, verdict, investigated_by, created_at.

Create a query to insert a log entry after each lookup:

INSERT INTO investigation_log (query, type, risk_score, verdict, investigated_by, created_at)
VALUES (
  {{ searchInput.value }},
  {{ lookupType.value }},
  {{ currentResult.value?.risk_score }},
  {{ currentResult.value?.verdict }},
  {{ current_user.email }},
  NOW()
)

Trigger this query in the investigate button's success handler. Then add a table that reads from investigation_log ordered by created_at DESC.

Test the dashboard

  1. Enter a phone number like 5551234567 in the search input
  2. Select "Phone Number" as the type
  3. Click Investigate
  4. The risk score gauge, verdict, explanation, and signals table should all populate
  5. Try a URL lookup by switching the type and entering https://example.com

Share the app with your ops team by clicking Share in the top right. You can set different permission levels for investigators (edit) and managers (admin).

Next Steps

  • Add batch investigation by accepting a CSV upload of phone numbers
  • Connect to your customer database to auto-populate the search when clicking a customer record
  • Add a notes field so investigators can document their findings
  • Set up a Retool Workflow to automatically investigate new signups every hour using the batch endpoint

On this page