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
- A Retool account (free tier works)
- A ScamVerify™ API key (get one at scamverify.ai/settings/api)
- Basic familiarity with Retool's interface
What You Will Build
An internal dashboard with:
- A search input for phone numbers or URLs
- A risk score gauge with color-coded verdict
- A detailed signals breakdown table
- An AI-generated explanation panel
- 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_hereContent-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:
- Top section: Search input and submit button
- 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, padding16px, border radius8px
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:
| Column | Key | Width |
|---|---|---|
| Source | source | 25% |
| Status | status | 15% |
| Details | details | 60% |
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
- Enter a phone number like
5551234567in the search input - Select "Phone Number" as the type
- Click Investigate
- The risk score gauge, verdict, explanation, and signals table should all populate
- 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