Skip to main content
DesignKey Studio

Financial Services, Fintech

Company Verification

How a fintech platform verifies a user's company name against OpenCorporates at onboarding, making a registry match the biggest input in its automated trust score.

Company Verification - Merchant Company Verification research hero

Key findings

  • A fintech platform checks each user's self-reported business name against OpenCorporates, the largest open database of company registrations, and treats an exact match as its strongest legitimacy signal.
  • A confirmed match is the single heaviest input in an automated user trust score, weighted ahead of an email-risk check, a phone-reputation check, and an IP-risk check.
  • The match is exact-name only: the typed name must equal the registered name after trimming and lowercasing, so "Acme, LLC" against a registry entry of "ACME LLC" is a miss.
  • The lookup walks pages of 100 results with a roughly one-second pause between pages, which makes a common company name slow to resolve and slow to give up on.
  • The service fails hard internally but the onboarding use-case swallows that error, so a failed lookup silently becomes not matched and scores zero, indistinguishable from a genuine no-match.

When a user signs up, the business name they type is a claim, not a fact. A payments platform that onboards users needs to know whether that name belongs to a company that actually exists on a public register somewhere. This is a look at how that platform answered the question with OpenCorporates: every user's doing-business-as name is searched against the world's largest open registry, and an exact match becomes the heaviest input in an automated trust score. The writeup covers what gets checked, how the match is made, where it is used, how it behaves across real sign-ups, and the compromises baked into the design.

What does OpenCorporates actually verify?

OpenCorporates verifies that a company name corresponds to a real, registered legal entity somewhere in the world, by matching it against the largest open database of company registrations. It aggregates official company registers across many jurisdictions into one searchable dataset, so instead of querying dozens of national registries directly, the platform asks one API a single question: is there a registered company that goes by this name?

The signal it produces is narrow but valuable. A match does not prove the person signing up controls that company, and it does not run a full financial background check. What it does prove is that the business name is not invented - that somewhere a real filing exists under that name. For a platform onboarding users, that is a meaningful first cut between a legitimate business and a fabricated one.

This is the "Know Your Business" (KYB) counterpart to the more familiar Know Your Customer check. Where KYC confirms a person, KYB confirms a company: that it is registered, and that the name a user gives matches a real entity. It complements the person-level identity check the same platform runs, covered in the identity verification writeup. According to OpenCorporates, its dataset is drawn from official public registers rather than self-reported data, which is what makes a match a trustworthy signal rather than a circular one.

Why does a user's company name matter for fraud?

A user's company name matters because it is self-reported at sign-up and easy to fabricate, yet it anchors everything the platform will later believe about that account. Users type in a doing-business-as name, or DBA - the public name a business trades under, which may differ from its owner's legal name - with no built-in guarantee that the business is real. A fabricated or misremembered name is a quiet signal that the rest of the application deserves a closer look.

The cost of not checking is misplaced trust and wasted human effort. Without an automatic legitimacy signal, an operator has to eyeball each new user and decide whether the business sounds real, which does not scale and is easy to get wrong. Anchoring one hard, external check at the moment of sign-up means a name that matches nothing on any public register surfaces on its own, before anyone spends time on the lead.

The company name is also a good early anchor because it is available up front and can be verified against an independent source. Unlike a claim the platform can only take at face value, a business name can be tested against a public register right away - which is exactly what makes it the heaviest input rather than a footnote. It sits alongside the platform's other automated checks, covered in more detail in the user email risk scoring writeup.

How does the platform check a company name at onboarding?

The platform sends the user's DBA to OpenCorporates during onboarding, searches for an exact name match, and records the result on the user record. The credentials and base URL live in configuration loaded from a secrets store, and the search itself is a paginated loop that walks results until it either finds an exact match or runs out of pages.

User DBA self-reported at sign-up Onboarding service requests one lookup OpenCorporates walks pages of 100, exact name match Matched? sets flag + company number Trust score the single heaviest input of the four with email, phone, and IP checks sharing the rest Zoho CRM and exports Matched_on_OpenCorporates and OpenCorporates_Company_ID, plus a lead-export column

The DBA is searched once at onboarding; a match sets a flag and a company number, which then feed the trust score and the CRM.

The integration is a small backend service with one job: given a company name, return the matching company or nothing. It reads its API token from AWS Systems Manager Parameter Store rather than from committed code, injects a ConfigService and an HTTP client, and searches /companies/search ordered by relevance score, 100 results per page. It walks the pages looking for a company whose name equals the DBA after trimming and lowercasing, returns the first exact match, and returns null only after the last page yields nothing. A short sleep between pages keeps it inside the provider's rate limits.

interface OpenCorporatesCompany {
  name: string;
  company_number: string;
}

@Injectable()
export class OpenCorporatesService {
  private readonly logger = new Logger(OpenCorporatesService.name);
  private readonly baseUrl = 'https://api.opencorporates.com/v0.4';

  constructor(
    private readonly http: HttpService,
    private readonly config: ConfigService, // apiKey loaded from SSM Parameter Store at boot
  ) {}

  /** Walk pages of 100 until we find an exact name match, or run out of pages. */
  async searchCompany(companyName: string): Promise<OpenCorporatesCompany | null> {
    const target = companyName.trim().toLowerCase();
    const apiToken = this.config.get<string>('openCorporates.apiKey');

    try {
      for (let page = 1; ; page++) {
        const url = `${this.baseUrl}/companies/search`;
        const params = { q: companyName, order: 'score', per_page: 100, page, api_token: apiToken };
        const { data } = await firstValueFrom(this.http.get(url, { params }));

        const companies: OpenCorporatesCompany[] = data.results.companies.map((c) => c.company);
        const match = companies.find((c) => c.name.trim().toLowerCase() === target);
        if (match) return match;

        if (page >= data.results.total_pages) return null; // no exact match anywhere
        await new Promise((resolve) => setTimeout(resolve, 1000)); // rate-limit guard
      }
    } catch (err) {
      // Fail hard: the caller decides whether to swallow this.
      this.logger.error(`OpenCorporates lookup failed for "${companyName}"`, err);
      throw new HttpException('OpenCorporates lookup failed', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}

Two design choices stand out. First, the search is deliberately strict: it only accepts a match when the registered name equals the typed name character for character (after trimming and lowercasing), which trades false positives for false negatives. Second, the API token is loaded asynchronously from Parameter Store at boot, so the credential is never committed and can be rotated without a redeploy - the pattern AWS Systems Manager Parameter Store exists to support.

How is a company matched, and where does exact matching break?

A company is matched only when the user's typed name is character-for-character identical to a registered name, after both are trimmed of surrounding whitespace and lowercased. There is no fuzzy comparison, no punctuation normalization, and no allowance for a different legal suffix. The moment the two strings diverge, the loop keeps looking, and if no page contains an identical string, the user is recorded as not matched.

Why an exact match misses a real company AS TYPED BY USER Acme, LLC ON THE PUBLIC REGISTER ACME LLC Trim and lowercase both sides: "acme, llc" "acme llc" the comma still differs Exact comparison: no match Loop walks every page, returns null. A real, registered company scores nothing on the heaviest check. Normalized comparison: match Strip punctuation and fold suffixes (LLC, Inc, Co) and the two resolve to the same company.

Case is handled, but punctuation and suffix differences are not: "Acme, LLC" and "ACME LLC" fail an exact comparison even though they are the same business.

This is where the design is brittle. Real businesses write their names inconsistently: "Acme, LLC" versus "Acme LLC" versus "ACME LLC", "The Acme Company" versus "Acme Co", "Smith & Sons" versus "Smith and Sons". Every one of those is a legitimate company that an exact-name search can miss. Because the platform stores only a match or no-match, a near-miss and a genuinely fabricated name look identical downstream. The false-negative rate is the cost of never producing a false positive, and for the heaviest signal in the score that is a large cost to leave unmanaged.

How much does an OpenCorporates match weigh in the trust score?

An OpenCorporates match is the heaviest single input in the automated trust score, weighted above any other individual check. The scoring is binary: isMatchedOnOpenCorporates ? WEIGHT : 0. There is no partial credit for a close-but-not-exact name and no confidence gradient; a user either clears the check for its full weight or contributes nothing from it.

The composite blends four independent checks: the OpenCorporates company match, plus an email-risk check, a phone-reputation check, and an IP-risk check. Each is scored independently and summed, so a user who fails the company match starts well behind regardless of how clean the other three look. That is by design - a business the platform cannot find on any public register is a substantial concern - but it also means the heaviest input is the one most exposed to the exact-match brittleness described above.

Weighting company verification highest reflects a judgment about signal quality: a registry match is harder to fake than an email address or a phone number, so it earns the most trust when it succeeds. The flip side is that the check with the most influence is also the one whose failure modes are least visible, which is what makes the swallowed-error behavior below worth understanding.

Where does the match show up for the team?

The match result follows the user into the tools the team already uses, so nobody has to re-query OpenCorporates by hand. When a match is found, the service updates the user with isMatchedOnOpenCorporates: true and stores the registered entity's company_number as openCorporatesCompanyId. Both values sync to the Zoho CRM as Matched_on_OpenCorporates and OpenCorporates_Company_ID, and the flag rides along in lead exports.

Surfacing the result in the CRM and exports means an operator working a lead can see at a glance whether the business checked out, and can pivot to the stored company number to look the entity up directly. (CRM here means the customer-relationship-management system where the team works leads.) The point is that a legitimacy signal computed once at onboarding stays visible in the normal flow of work, rather than living only inside the trust-score math where nobody reads it.

How does it behave across three real scenarios?

The design is easiest to understand through the sign-ups it was built for. Three cases cover the range.

Scenario 1: a clean, exact match

A user signs up with a business name that matches a registered entity exactly. The search finds the company on an early page, isMatchedOnOpenCorporates is set to true, the company number is stored, and the check contributes its full weight to the trust score. The result syncs to the CRM as matched, and an operator sees a strong, confirmed signal. This is the happy path, and it is fast because the exact match is found before the loop has to walk far.

Scenario 2: a real company with a punctuation or suffix mismatch

A user types "Acme, LLC" but the public register holds "ACME LLC". The loop lowercases and trims both, but the comma still breaks the comparison, so it never finds an identical string and walks every page before returning null. The user is recorded as not matched and loses the score's heaviest input, despite operating a perfectly legitimate, registered business. Nothing distinguishes this false negative from a fabricated name, so a genuine user looks materially riskier than they are - and the search was also slow, because it had to exhaust the pagination before giving up.

Scenario 3: the lookup errors, or a common name is never found

The lookup call fails - a timeout, a rate-limit rejection, or a transient error - or the DBA is a common name that never resolves to an exact match across many pages. On an error, the service throws a 500, but an outer try/catch in the onboarding use-case swallows it, so onboarding continues and the user is left as not matched. The outcome is a silent zero that is indistinguishable from a genuine no-match: the trust score cannot tell "we could not check" apart from "we checked and found nothing", and neither can an operator reading the CRM.

What are the risks and gotchas?

The design makes several deliberate compromises, and knowing them matters for anyone auditing the trust score or extending the integration.

Exact-name matching is brittle. Case and surrounding whitespace are normalized, but punctuation and legal suffixes are not. "Acme, LLC", "Acme LLC", and "ACME, L.L.C." are the same business to a human and three different strings to the comparison. The result is a high false-negative rate on legitimate companies, concentrated in exactly the check that matters most.

The paginated loop is slow when it fails. A common name can appear across many pages of 100 results without ever producing an exact match, and the loop pauses roughly a second between pages to respect rate limits. A name that will ultimately not match is therefore the slowest case, because the loop only stops at the last page. The latency lands at onboarding, the worst moment to be slow.

Fail-hard-but-swallowed hides real failures. The service throws on error, which is honest, but the calling use-case catches and ignores that throw so onboarding is never blocked. The safety is real, but the side effect is that a failed lookup and a genuine no-match both end as "not matched" and both score zero. An audit reading the trust score after the fact cannot tell the two apart without also reading the logs.

Scoring is binary with no confidence. The check yields its full weight or nothing, with nothing in between. A near-match with one character of difference scores the same as a fabricated name. There is no room to express "probably this company" or "matched, but low confidence", so the richest part of the signal - how close the match was - is discarded.

Coverage varies by jurisdiction. OpenCorporates aggregates many registers, but coverage and freshness differ across countries and states. A perfectly real business in a thinly covered jurisdiction can be absent from the dataset entirely, producing a legitimate-looking no-match that has nothing to do with the user's honesty.

What would we do differently?

The single most valuable change would be to normalize names before comparing them. Stripping punctuation, collapsing whitespace, and folding common legal suffixes (LLC, L.L.C., Inc, Incorporated, Co, Company) would catch the large class of false negatives that today cost legitimate users the score's heaviest input. Pairing that with a fuzzy or token-based comparison would catch reordered words and minor spelling drift too.

Three further changes follow naturally. Store a confidence score instead of a bare boolean, so a near-match can earn partial credit and the trust score can reflect how strong the match actually was. Distinguish "lookup failed" from "no match found" with an explicit status, so a swallowed error stops masquerading as a genuine zero and auditors get an honest picture. And cap the pagination at a sensible page limit, so a common name that is never going to match does not spend many seconds walking every page at onboarding. The check earns its weight as-is; these changes are about closing the false-negative and silent-failure gaps that a high-weight signal can least afford. Teams weighing this kind of build-versus-refine decision often start with a short business analysis engagement or a focused API integration sprint.

Frequently asked questions

What is the difference between KYC and KYB?

KYC (Know Your Customer) confirms that a person is who they claim to be. KYB (Know Your Business) confirms that a company is real and registered, and that the name a user gives matches a genuine entity. This integration is a KYB check: it verifies the business, not the individual, by matching the user's company name against OpenCorporates.

Does a failed OpenCorporates match block a user from signing up?

No. A no-match never blocks onboarding on its own. It lowers the user's automated trust score and shows up in the CRM as not matched, but the decision to slow down or reject a user stays with people and the composite signal, not with the company check alone.

Why is the match exact-name only, and what does that miss?

The search only accepts a company whose registered name equals the typed name after trimming and lowercasing. It trades false positives for false negatives: it will not match the wrong company, but it misses real ones whose names differ by punctuation or legal suffix, such as "Acme, LLC" against a register entry of "ACME LLC".

What happens if the OpenCorporates lookup errors during sign-up?

Onboarding continues. The service throws a 500 on error, but the onboarding use-case swallows that error, so the user is simply recorded as not matched. The trade-off is that a failed lookup is treated the same as a genuine no-match - both score zero - so an outage can make real users look riskier until it is resolved.

How much does the company match influence the trust score?

It is the single heaviest input in the composite. The trust score blends four independent checks: the OpenCorporates match, plus an email-risk check, a phone-reputation check, and an IP-risk check. A confirmed match is the strongest positive signal a user can earn.

Is the company number stored, and where does it go?

Yes. On a match, the registered entity's company number is stored on the user as openCorporatesCompanyId and synced to the Zoho CRM as OpenCorporates_Company_ID, alongside the Matched_on_OpenCorporates flag. Both also appear in lead exports, so an operator can look the entity up directly.

Sources and further reading

What's next

The OpenCorporates check does one well-defined job today: a hard, external read on whether a user's business name belongs to a real registered company, the heaviest input in the automated trust score. The clearest next steps are to normalize names before matching so legitimate businesses stop failing on punctuation, to store a confidence score instead of a bare boolean, and to separate a failed lookup from a genuine no-match. If you are wiring third-party verification into an onboarding flow and want it to be both resilient and honest about uncertainty, get in touch - it is the kind of integration work we do often.

Your next project?

Whether it's an internal tool for your company or a highly available Software-as-a-Service - we help you to get your ideas off the ground!