L2A matching answers a simple but critical question: does this new lead belong to an existing account?
If yes, the lead should be routed to the account owner (or a designated backup user). If not, the lead moves through a set of business rules—like territory, region, or lead source—to find its rightful owner.
Without L2A matching, you risk:
- Duplicate records that muddy reporting and operations.
- Confused customers when multiple reps reach out independently.
- Inefficient sales cycles when leads are constantly reassigned.
Imagine this: a customer attends your webinar. Salesforce creates a new lead, even though they’re already working with an account executive. Without L2A matching, that lead might be routed to a BDR instead, leading to fragmented communication. With proper L2A matching, the lead goes straight to the right AE, keeping the customer experience seamless.
Of course, there are dedicated tools, such as LeanData, that include lead-to-account matching logic built in, among other things, but many organizations either can’t afford or don’t have the technical proficiency to correctly configure this type of tool. In this case, it is possible to build a custom version of lead routing and L2A matching custom in Salesforce.
Step 1: Normalization for Clean Matching
Matching is only as good as the data you’re comparing. Company names, addresses, and domains rarely come in clean—“Acme, Inc.” versus “ACME Corporation” will fail a literal text match.
That’s why normalization—standardizing data into a consistent format—is essential. Typical normalization steps include:
- Removing special characters (Acme, Inc. → Acme Inc)
- Lowercasing everything (ACME → acme)
- Expanding or unifying abbreviations (St. → Street)
- Trimming whitespace
Example:
| Field Name | Lead Value | Account Value | Normalized Value |
|---|---|---|---|
| Company Name | Acme, Inc. | ACME Corporation | acme |
| Website | www.acme.com | acme.com | acme |
| Address | 123 Elm St. | 123 Elm Street | 123elmstreet |
By normalizing values on both Leads and Accounts and storing the normalized values on dedicated fields, you make matching logic clean and fast with simple comparison operations between the fields on the 2 objects.
Normalization is one of those things best handled as invocable apex. Maybe you could built it in flow somehow, but it would be complex and clunky, while this kind of use case is exactly where code shines. The below is an example apex class that will normalize inputs and return the normalized outputs to a flow that invoked it, so you can use a record-triggered flow on Lead and Account to invoke the same Apex class and use it as a consistent normalization engine.
public with sharing class NormalizeLeadToAccount {
@InvocableMethod(label='Normalize Lead and Account Data')
public static List<NormalizedResult> normalizeData(List<NormalizeRequest> requests) {
List<NormalizedResult> results = new List<NormalizedResult>();
for (NormalizeRequest req : requests) {
NormalizedResult result = new NormalizedResult();
result.normalizedCompanyName = normalizeCompanyName(req.companyName);
result.normalizedAddress = normalizeAddress(req.address);
result.normalizedPhone = normalizePhone(req.phone);
result.normalizedDomain = normalizeDomain(req.domain);
results.add(result);
}
return results;
}
private static String normalizeCompanyName(String companyName) {
String normalized = companyName.toLowerCase().replaceAll('[^a-zA-Z0-9]', '');
normalized = normalized.replaceAll('\\b(inc|corp|llc|ltd|gmbh|sa|ag|bv|nv|pvtltd|sl|kk|oy|spa|ab|sarl|ulc|cv|sc|scp|jsc|ooo)\\b', '');
return normalized;
}
private static String normalizeAddress(String address) {
address = address.toLowerCase();
Map<String, String> replacements = new Map<String, String>{
'st' => 'street', 'rd' => 'road', 'ave' => 'avenue', 'blvd' => 'boulevard'
};
for (String key : replacements.keySet()) {
address = address.replaceAll('\\b' + key + '\\b', replacements.get(key));
}
return address.replaceAll('\\s+', '');
}
private static String normalizePhone(String phone) {
phone = phone.replaceAll('[^0-9]', '');
return phone.startsWith('1') && phone.length() == 11 ? phone.substring(1) : phone;
}
private static String normalizeDomain(String domain) {
domain = domain.toLowerCase().replaceAll('^(https?://)?(www\\.)?', '');
return domain.contains('.') ? domain.split('\\.')[0] : domain;
}
public class NormalizeRequest {
@InvocableVariable public String companyName;
@InvocableVariable public String address;
@InvocableVariable public String phone;
@InvocableVariable public String domain;
}
public class NormalizedResult {
@InvocableVariable public String normalizedCompanyName;
@InvocableVariable public String normalizedAddress;
@InvocableVariable public String normalizedPhone;
@InvocableVariable public String normalizedDomain;
}
}
Step 2: Matching Logic in Flow
The actual matching should happen declaratively in Flow after the normalized values have been stamped on each object:
Trigger: Lead creation or update.
Normalize Data: Call the Apex class above.
Match to Account:
- Use Get Records to query Accounts where normalized values overlap.
- Use conditional logic like:
- Website match OR (Name match AND (Address match OR Phone match)) OR (Address match AND Phone match)
- Limit to 1 record, sorted by last modified date or last activity date
Decision:
- If match found → Assign to account owner.
- If no match → Move to geo-routing.
Step 4: Geo-Based Routing with Custom Metadata
When no account match is found, routing falls back to geo rules or business logic defined in Custom Metadata.
Create a CMT called Lead_Routing_Rule__mdt with fields like:
- Region (Text)
- Lead Source (Picklist)
- Assigned Owner (User ID or Queue ID)
- Index (Number for prioritization)
- Active (Checkbox for enable/disable)
In Flow:
- Query Lead_Routing_Rule__mdt records filtered by region, source, and active status.
- Sort by Index and pick the first result.
- Assign the lead owner accordingly.
Step 5: Sync vs Async Processing
- Synchronous (real-time, recommended for user-created leads): ensures routing and ownership happen immediately.
- Asynchronous (Queueable Apex, Platform Events): best for high-volume imports, prevents hitting governor limits.
Step 6: Handling Conversion
Salesforce’s out-of-the-box lead conversion doesn’t automatically honor L2A matches. To fix this:
- Store the matched Account ID in a custom field on Lead.
- Use validation rules to enforce conversion into that same Account.
- Add user-friendly error messages for mismatches.
This ensures data integrity and prevents “orphan” conversions.
Wrapping Up
A well-designed L2A matching and routing system in Salesforce blends automation with flexibility:
- Apex handles the heavy lifting of normalization.
- Flow manages matching and routing decisions.
- Custom Metadata gives admins control to update routing rules without code.
The result? Cleaner data, happier customers, and more efficient sales teams. Leads stop falling through the cracks, and sales reps spend less time untangling ownership conflicts and more time selling.