Suppose you've already collected a CRM email base of customers who made a purchase. Cold traffic gives a CPA above 1500₽, and you need to scale cheaper. The solution is lookalike audiences built on a segment of best buyers. But many upload "raw" lists—all random registrations. The algorithm builds a similar audience, but it yields a CPA no lower than the original. The secret lies in a high-quality seed segment: only those who brought real revenue. Below is a proven pipeline with real numbers and ready code.
Segments for seed audiences: which work best
The quality of the resulting audience directly depends on the seed data. The more accurately we describe the "portrait" of a converted user, the higher the precision. Here are typical segments:
| Segment | Minimum size | Quality |
|---|---|---|
| Buyers in last 90 days | 500–1000 | Very high |
| Users with LTV > 5000₽ | 500 | High |
| Completed trial → paid plan | 300 | High |
| Viewed key pages | 1000+ | Medium |
| All registered users | 5000+ | Low |
A segment of 500 buyers gives 3x higher accuracy than a list of 5000 random registrations. Therefore, we always start with CRM analysis and selecting the core—users with the highest LTV. Additionally, we enrich data through CRM and event analytics to exclude inactive contacts.
In one e-commerce project with a turnover of 50 million ₽, we isolated a segment of buyers with LTV > 5000₽ (800 records total). After creating a lookalike, CPA dropped from 1200₽ to 750₽, and conversion increased by 40%. The key was a high-quality seed segment.
To choose the platform, compare the main parameters of Meta and VK:
| Parameter | Meta (Facebook/Instagram) | VK Ads |
|---|---|---|
| Minimum seed size | 200 records | 500 records |
| Data format | SHA-256 only | Open or SHA-256 with prefix |
| Lookalike size | 1%–10% of country | Up to 1,000,000 |
| Auto-update | Via API | Via import |
Why hashing contacts is critical for lookalike?
Before sending to ad systems, emails and phones must be normalized and hashed with SHA-256. Otherwise, the system will reject the data or reduce match quality. In Laravel, this is done with one class—see example below.
Why SHA-256 specifically?
It is an irreversible hash function recommended by ad platforms. It transforms data into a fixed-length string that cannot be reversed. More about SHA-256 can be read in Wikipedia.class AudienceExporter
{
public function exportHighValueCustomers(): array
{
return User::query()
->join('orders', 'orders.user_id', '=', 'users.id')
->where('orders.status', 'completed')
->where('orders.created_at', '>=', now()->subDays(90))
->groupBy('users.id', 'users.email', 'users.phone')
->havingRaw('SUM(orders.total) >= ?', [5000])
->get(['users.email', 'users.phone'])
->map(fn($user) => [
'email' => hash('sha256', strtolower(trim($user->email))),
'phone' => $user->phone ? hash('sha256', $this->normalizePhone($user->phone)) : null,
])
->toArray();
}
private function normalizePhone(string $phone): string
{
$digits = preg_replace('/\D/', '', $phone);
if (strlen($digits) === 10) $digits = '7' . $digits;
return '+' . $digits;
}
public function exportToCsv(array $data, string $filename): string
{
$path = storage_path("app/audiences/{$filename}.csv");
$fp = fopen($path, 'w');
fputcsv($fp, ['email', 'phone']);
foreach ($data as $row) {
fputcsv($fp, [$row['email'], $row['phone'] ?? '']);
}
fclose($fp);
return $path;
}
}
How to upload an audience to Meta (Facebook/Instagram)?
Meta provides the Custom Audiences API. To upload, follow these steps:
- Create a custom audience via API.
- Transfer hashed emails in batches of up to 10,000 records.
- Build a lookalike based on this audience.
Sequence: create custom audience -> upload hashes -> build lookalike. It's important to specify the schema EMAIL_SHA256. The code below does this automatically.
class MetaAudienceService
{
private const API_VERSION = 'v19.0';
private string $accessToken;
private string $adAccountId;
public function createCustomAudience(string $name, string $description): string
{
$response = Http::post(
"https://graph.facebook.com/{$this->API_VERSION}/act_{$this->adAccountId}/customaudiences",
[
'name' => $name,
'subtype' => 'CUSTOM',
'description' => $description,
'customer_file_source' => 'USER_PROVIDED_ONLY',
'access_token' => $this->accessToken,
]
);
return $response->json('id');
}
public function uploadUsers(string $audienceId, array $hashedEmails): void
{
foreach (array_chunk($hashedEmails, 10000) as $chunk) {
$payload = [
'schema' => ['EMAIL_SHA256'],
'data' => array_map(fn($email) => [$email], $chunk),
];
Http::post(
"https://graph.facebook.com/{$this->API_VERSION}/{$audienceId}/users",
[
'payload' => json_encode($payload),
'access_token' => $this->accessToken,
]
);
}
}
public function createLookalike(string $sourceAudienceId, string $country, float $ratio = 0.01): string
{
$response = Http::post(
"https://graph.facebook.com/{$this->API_VERSION}/act_{$this->adAccountId}/customaudiences",
[
'name' => "Lookalike {$country} {$ratio}",
'subtype' => 'LOOKALIKE',
'origin_audience_id' => $sourceAudienceId,
'lookalike_spec' => json_encode([
'type' => 'similarity',
'country' => $country,
'ratio' => $ratio,
]),
'access_token' => $this->accessToken,
]
);
return $response->json('id');
}
}
Uploading an audience to VK Ads
VK accepts emails both in open form and hashed (with a prefix). Algorithm: create a retargeting group -> import contacts. Note: business accounts have their own limits, but the basic pipeline is the same.
class VkAudienceService
{
public function uploadToRetargeting(string $name, array $hashedEmails): int
{
$content = implode("\n", $hashedEmails);
$response = Http::withToken($this->token)
->post('https://api.vk.com/method/ads.createTargetGroup', [
'account_id' => $this->accountId,
'name' => $name,
'v' => '5.199',
]);
$groupId = $response->json('response.id');
Http::withToken($this->token)
->post('https://api.vk.com/method/ads.importTargetContacts', [
'account_id' => $this->accountId,
'target_group_id' => $groupId,
'contacts' => $content,
'v' => '5.199',
]);
return $groupId;
}
}
Automating lookalike audience updates
Lookalike audiences become outdated as new conversions appear. Manual export every week is unnecessary. We set up automatic updates via Schedule—it works like clockwork.
class UpdateLookalikeAudiences implements ShouldQueue
{
public function handle(): void
{
$exporter = app(AudienceExporter::class);
$data = $exporter->exportHighValueCustomers();
$emails = array_column($data, 'email');
app(MetaAudienceService::class)->uploadUsers(
config('ads.meta.buyer_audience_id'),
$emails
);
Log::info("Lookalike audience updated", ['count' => count($emails)]);
}
}
Step-by-step setup algorithm
- Analyze existing site data (CRM, forms, events)—isolate the seed segment.
- Develop an export module with hashing and validation.
- Integrate with selected ad platforms (Meta, VK).
- Configure automatic updates via cron or scheduler.
- Monitor—log errors and audience freshness.
What's included in the work
- Data analysis and selection of the optimal seed segment.
- Development of an exporter with SHA-256 hashing and support for Meta and VK formats.
- Integration with ad platform APIs (one or several).
- Setup of automatic weekly updates.
- Documentation and post-launch consultation.
Timeline and how to get started
The audience export system with hashing and automatic updates for one platform takes 4–6 working days. For multiple platforms, the timeline extends to 8–10 days. We have been working with lookalike audiences for over 5 years and have completed 30+ successful projects—this guarantees reliability and quality.
Order the export system setup—we will prepare the seed segment, configure integration, and set up auto-updates. Contact us for a consultation.







