Gig Economy & Marketplace Fraud
Detect multi-accounting, fake sellers, and fraudulent listings on marketplace and gig platforms.
Multi-accounting is the top fraud vector for gig economy platforms. 73% of marketplace fraud teams rank it among their top concerns. Fraudsters cycle through disposable VoIP numbers to create dozens of accounts, exploit promotional offers, manipulate reviews, and evade bans. Traditional OTP verification catches none of this.
The Problem
Standard phone verification (send SMS, user enters code) only proves someone can receive a text at that number right now. It tells you nothing about whether the number is:
- A disposable VoIP number purchased for $0.50
- Tied to 15 other accounts on your platform
- Associated with FTC fraud complaints
- Operated by a carrier known for enabling fraud
Fraudsters on platforms like TaskRabbit, Thumbtack, and Fiverr use cheap VoIP numbers from high-risk carriers to create throwaway accounts. They complete a few jobs to build ratings, then scam customers through off-platform payments or no-show bookings.
How ScamVerify™ Helps
The ScamVerify™ API adds a risk intelligence layer on top of your existing OTP flow. Before sending the OTP (or after verifying it), check the number against ScamVerify™ to assess its fraud potential.
- Line type detection separates real mobile numbers from disposable VoIP
- Carrier risk scoring identifies the 18 high-risk VoIP carriers most used for fraud
- FTC complaint history reveals numbers with prior scam reports
- Risk scoring synthesizes all signals into a single 0-100 score
Decision Flow for Onboarding
User submits phone number
|
v
Call ScamVerify™ Phone Lookup
|
v
line_type = "voip"? ──────────────────────────> Flag for review
| |
v v
risk_score < 20? ──> APPROVE High-risk carrier?
| |
v Yes: REJECT
risk_score < 50? ──> APPROVE with monitoring No: APPROVE with
| enhanced monitoring
v
risk_score >= 50? ──> REJECTCode Example: Onboarding Verification
async function verifyGigWorkerPhone(phoneNumber) {
const response = await fetch('https://scamverify.ai/api/v1/phone/lookup', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SCAMVERIFY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone_number: phoneNumber }),
});
if (!response.ok) {
throw new Error(`ScamVerify API error: ${response.status}`);
}
const result = await response.json();
const isHighRiskVoip =
result.signals.line_type === 'voip' && result.risk_score >= 40;
const hasComplaints = result.signals.ftc_complaints > 0;
const isRobocaller = result.signals.robocall_detected === true;
// Tiered decision logic
if (result.risk_score >= 60 || isRobocaller) {
return {
decision: 'REJECT',
reason: result.explanation,
riskScore: result.risk_score,
signals: {
lineType: result.signals.line_type,
carrier: result.signals.carrier,
ftcComplaints: result.signals.ftc_complaints,
robocall: result.signals.robocall_detected,
},
};
}
if (isHighRiskVoip || hasComplaints) {
return {
decision: 'REVIEW',
reason: 'Phone number requires additional identity verification',
riskScore: result.risk_score,
requireIdUpload: true,
signals: {
lineType: result.signals.line_type,
carrier: result.signals.carrier,
ftcComplaints: result.signals.ftc_complaints,
},
};
}
return {
decision: 'APPROVE',
riskScore: result.risk_score,
monitoring: result.risk_score >= 20 ? 'enhanced' : 'standard',
};
}
// Integration with your signup endpoint
app.post('/api/workers/register', async (req, res) => {
const { phone, email, name } = req.body;
const verification = await verifyGigWorkerPhone(phone);
switch (verification.decision) {
case 'REJECT':
await logRejectedSignup({ phone, email, ...verification });
return res.status(400).json({
error: 'We could not verify this phone number. Please try a different number.',
code: 'PHONE_VERIFICATION_FAILED',
});
case 'REVIEW':
const account = await createAccount({
phone, email, name,
status: 'pending_verification',
riskScore: verification.riskScore,
});
// Require government ID upload before activation
return res.json({
accountId: account.id,
status: 'pending_verification',
nextStep: 'id_upload',
message: 'Please upload a government-issued ID to complete verification.',
});
case 'APPROVE':
const approved = await createAccount({
phone, email, name,
status: 'active',
riskScore: verification.riskScore,
monitoringLevel: verification.monitoring,
});
return res.json({
accountId: approved.id,
status: 'active',
});
}
});import requests
import os
def verify_gig_worker_phone(phone_number: str) -> dict:
response = requests.post(
"https://scamverify.ai/api/v1/phone/lookup",
headers={
"Authorization": f"Bearer {os.environ['SCAMVERIFY_API_KEY']}",
"Content-Type": "application/json",
},
json={"phone_number": phone_number},
)
response.raise_for_status()
result = response.json()
is_high_risk_voip = (
result["signals"]["line_type"] == "voip"
and result["risk_score"] >= 40
)
has_complaints = result["signals"]["ftc_complaints"] > 0
is_robocaller = result["signals"].get("robocall_detected", False)
if result["risk_score"] >= 60 or is_robocaller:
return {
"decision": "REJECT",
"reason": result["explanation"],
"risk_score": result["risk_score"],
"signals": {
"line_type": result["signals"]["line_type"],
"carrier": result["signals"]["carrier"],
"ftc_complaints": result["signals"]["ftc_complaints"],
},
}
if is_high_risk_voip or has_complaints:
return {
"decision": "REVIEW",
"reason": "Phone number requires additional identity verification",
"risk_score": result["risk_score"],
"require_id_upload": True,
}
return {
"decision": "APPROVE",
"risk_score": result["risk_score"],
"monitoring": "enhanced" if result["risk_score"] >= 20 else "standard",
}Key Signals for Gig Platforms
| Signal | What It Means | Recommended Action |
|---|---|---|
line_type: "voip" + high-risk carrier | Disposable number likely purchased for multi-accounting. | Require government ID upload before activation. |
line_type: "voip" + low-risk carrier | Could be Google Voice or a legitimate VoIP user. Not inherently suspicious. | Approve with enhanced monitoring for the first 30 days. |
ftc_complaints > 0 | The number has been reported for scam activity. | Reject or require in-person verification. |
robocall_detected: true | The number is associated with automated calling campaigns. Strong fraud signal. | Reject automatically. |
risk_score < 20 + line_type: "mobile" | Clean mobile number with no negative history. Lowest risk. | Approve with standard monitoring. |
risk_score: 20-50 | Some minor signals present. May be benign. | Approve but flag for review if chargebacks or complaints appear within 30 days. |
Fake Listing Detection
For marketplace platforms, scan URLs and phone numbers embedded in listing descriptions to catch off-platform scam attempts.
async function scanListingContent(listing) {
const flags = [];
// Check any phone numbers in the listing description
const phoneRegex = /\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
const phones = listing.description.match(phoneRegex) || [];
for (const phone of phones) {
const check = await fetch('https://scamverify.ai/api/v1/phone/lookup', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SCAMVERIFY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone_number: phone }),
}).then(r => r.json());
if (check.risk_score >= 40) {
flags.push({
type: 'suspicious_phone_in_listing',
value: phone,
riskScore: check.risk_score,
});
}
}
// Check any URLs in the listing description
const urlRegex = /https?:\/\/[^\s]+/g;
const urls = listing.description.match(urlRegex) || [];
for (const url of urls) {
const check = await fetch('https://scamverify.ai/api/v1/url/lookup', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SCAMVERIFY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
}).then(r => r.json());
if (check.risk_score >= 30) {
flags.push({
type: 'suspicious_url_in_listing',
value: url,
riskScore: check.risk_score,
});
}
}
return { safe: flags.length === 0, flags };
}Batch processing tip. If you need to scan hundreds of listings, spread your API calls over time to stay within rate limits. The ScamVerify™ API caches results, so re-checking the same phone number or URL within 24 hours returns instantly without consuming additional quota.
Multi-Accounting Detection Strategy
Phone verification is one layer. For comprehensive multi-accounting detection, combine ScamVerify™ results with your own data:
- At signup: Check the phone number with ScamVerify™. Store the carrier, line type, and risk score.
- Cross-reference internally: Does this carrier and line type pattern match any recently banned accounts?
- Monitor early behavior: Accounts that pass verification but show suspicious activity in the first 7 days (rapid review accumulation, off-platform payment requests) should be re-checked.
- Periodic re-verification: Run existing accounts through ScamVerify™ quarterly. Numbers that were clean at signup can develop complaint histories over time.
Best Practices
- Never reject on VoIP alone. Many gig workers legitimately use Google Voice for business. Combine line type with carrier risk, complaint history, and your own behavioral data.
- Gate payouts, not signups. Consider allowing VoIP signups but holding first payouts until identity is verified. This reduces friction while still protecting against fraud.
- Store the full API response. Keep the risk score, signals, and explanation alongside each user record for dispute resolution and pattern analysis.
Getting Started
- Create an API key and set up your account
- Follow the Quickstart guide to make your first lookup
- Review the Phone Lookup API Reference for full request and response schemas
- Implement the onboarding verification flow above and tune thresholds based on your fraud data