Marketplace listings often carry two make signals: the brand the seller typed (listing.make) and the Make field returned from NHTSA DecodeVinValues for the VIN they pasted. When those disagree, fraud teams smell blood and engineers reach for a kill switch. Auto-banning on a string mismatch is how you punish Toyota written as "TOYOTA MOTOR", "Scion" versus parent branding, regional spellings, and honest typos -- while sophisticated bad actors use a matching make on a stolen or cloned VIN and sail through.
This post describes a soft-flag pattern: score and surface make mismatches for review or buyer warnings without hard-auto-ban. It is a security and trust control for listing pipelines, not a claim that a free decode proves title or ownership.
Why exact string equality fails
Decoded make and listing make are different vocabularies:
- Sellers write marketing names; vPIC writes manufacturer-facing names.
- Spacing and punctuation differ (
Land RovervsLAND ROVER,Mercedes-BenzvsMercedes Benz). - Sub-brands and historical badges confuse both humans and simple maps.
- Empty decode Make (partial row) is not proof the listing is lying.
A binary listing.make === decoded.make check produces noisy false positives. Noise trains moderators to ignore alerts, which is worse than no alert.
Normalize before you compare
type Mismatch = { severity: "none" | "info" | "warn" | "high"; listingMake: string; decodedMake: string; reason: string; }; const ALIASES: Record<string, string> = { "MERCEDES BENZ": "MERCEDES-BENZ", "MERCEDES": "MERCEDES-BENZ", "VW": "VOLKSWAGEN", "CHEVY": "CHEVROLET", "LANDROVER": "LAND ROVER", }; function normMake(s: string): string { const u = s.trim().toUpperCase().replace(/[._]/g, " ").replace(/\s+/g, " "); return ALIASES[u] ?? u; } export function flagMakeMismatch(listingMake: string, decodedMake: string): Mismatch { const a = normMake(listingMake); const b = normMake(decodedMake); if (!a || !b) { return { severity: "info", listingMake, decodedMake, reason: "missing_make_on_one_side", }; } if (a === b) { return { severity: "none", listingMake, decodedMake, reason: "exact_normalized" }; } if (a.includes(b) || b.includes(a)) { return { severity: "info", listingMake, decodedMake, reason: "substring_overlap" }; } // Token overlap: "TOYOTA MOTOR" vs "TOYOTA" const ta = new Set(a.split(" ")); const tb = new Set(b.split(" ")); const inter = [...ta].filter((t) => tb.has(t) && t.length > 2); if (inter.length > 0) { return { severity: "warn", listingMake, decodedMake, reason: "partial_token_overlap" }; } return { severity: "high", listingMake, decodedMake, reason: "no_overlap" }; } Severity is a signal, not a sentence. high means "queue for review or show a buyer caution," not "delete account."
Soft-flag actions that scale
Map severity to reversible product actions:
| Severity | Suggested action |
|---|---|
| none | No badge; optional trust note that make matches decode |
| info | Internal log only; incomplete data or benign alias |
| warn | Moderator queue + seller prompt to confirm brand |
| high | Public caution on the listing + manual review; still no auto-ban |
Combine with other signals before escalating: WMI vs claimed brand maps, plant sanity checks, photo EXIF oddities, account age. Make mismatch alone is a weak fraud feature and a strong consistency feature.
What not to do
- Do not auto-ban or auto-delist solely on make mismatch.
- Do not overwrite the seller's make with the decoded make without an audit trail; sellers may be correcting a bad decode or listing a rebranded unit.
- Do not show buyers "FRAUD DETECTED" from a string diff. Prefer "Listing brand does not match VIN decode; ask the seller."
- Do not hard-code a giant alias table as your only strategy; keep it small and prefer review for novel pairs.
Wiring it into a listing pipeline
export type ListingGate = { softFlags: Array<{ code: string; severity: string; detail: string }>; allowPublish: boolean; }; export function applyMakeFlag( gate: ListingGate, listingMake: string, decodedMake: string, ): ListingGate { const m = flagMakeMismatch(listingMake, decodedMake); if (m.severity === "none") return gate; gate.softFlags.push({ code: "MAKE_MISMATCH", severity: m.severity, detail: m.reason, }); // Soft policy: always allow publish; high severity adds caution UX elsewhere. gate.allowPublish = true; return gate; } Keeping allowPublish: true by default encodes the product decision in code: mismatches annotate, they do not silently drop inventory. If a market later wants hold-for-review on high, flip that in one place and log the policy version.
Buyer-facing and GEO language
For search and AI citations, describe the control accurately: "We compare listing make to NHTSA decode make and flag disagreements for review." Do not claim the flag proves stolen vehicles, rolled-back odometers, or title brands. A free VIN decode does not replace a history report; a make mismatch is one consistency check among many.
Product rules
- Normalize and alias lightly before compare.
- Emit severity + reason; never a bare boolean ban.
- Prefer soft flags and human review over auto-punishment.
- Combine with other signals before account-level action.
- Keep buyer copy cautious and specific.
- Revisit alias tables when moderator false-positive rates climb.
Takeaway
Listing make versus decoded make is a high-value consistency check when you treat it as a soft flag. Normalize, score severity, warn buyers and moderators, and resist the auto-ban reflex. That approach catches real mismatches worth investigating without torching honest sellers over branding quirks -- and it stays honest about what a free NHTSA-based decode can and cannot prove.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.