Offline Conversion Import + Meta Ads Setup Guide
I audit offline conversion setups for Meta every month, and about 60% of them are broken. Usually it’s match rate issues (sub-30%) or delayed attribution because someone skipped the hashing step or used the wrong API endpoint. Meta’s offline conversion system is powerful when set up right, but the devil is in the data quality details.
What You’ll Have Working By The End
- Offline conversions flowing from your CRM/database directly to Meta Ads Manager
- Match rates of 40-65% (industry standard for well-configured setups)
- Attribution appearing in Meta within 24-48 hours of upload
- Automated daily/weekly upload schedule via API or manual CSV uploads
- Cross-verification system to track upload success and match rates
Prerequisites
- Meta Business Manager account with admin access
- Meta Ads account with conversion tracking permissions
- Access to your CRM/database with customer data (email, phone, address)
- Developer access or technical team for API implementation
- SSL certificate for any custom upload endpoints
Step 1: Configure Offline Event Set in Meta
First, you need to create an Offline Event Set in Meta Business Manager. This is where your offline conversions will be housed.
Navigate to Events Manager → Data Sources → Add New Data Source → Offline Conversions.
Critical configuration:
- Event Set Name: Use something descriptive like “CRM_Purchases_2026”
- Business: Select your business account
- Ad Account: Link to the specific ad account where you want attribution
- Offline Event Set ID: Save this — you’ll need it for the API calls
The Event Set ID looks like this: 1234567890123456. Copy it immediately because you’ll reference it in every API call.
Match Key Selection (this is where most setups break):
- Always enable:
email,phone - Enable if available:
fn(first name),ln(last name),ct(city),st(state),zip - Skip unless necessary:
country(only if you’re multi-country),dob(privacy concerns)
More match keys = higher match rates, but also higher privacy compliance burden.
Step 2: Set Up Data Processing and Hashing
Meta requires all PII to be SHA-256 hashed before transmission. I see unhashed data attempts in about 30% of broken setups.
Required hashing format:
- Remove all whitespace and convert to lowercase before hashing
- Phone numbers: Remove all non-numeric characters, include country code
- Emails: Lowercase, trim whitespace
- Names: Lowercase, remove extra spaces
Here’s the processing code I use for most client setups:
const crypto = require('crypto');
function hashForMeta(value, type) {
if (!value) return null;
let processed = value.toString().toLowerCase().trim();
if (type === 'phone') {
// Remove all non-numeric, add country code if missing
processed = processed.replace(/[^\d]/g, '');
if (processed.length === 10 && !processed.startsWith('1')) {
processed = '1' + processed; // Add US country code
}
}
if (type === 'email') {
processed = processed.toLowerCase().trim();
}
return crypto.createHash('sha256').update(processed).digest('hex');
}
// Usage example
const hashedEmail = hashForMeta('John.Doe@Example.com', 'email');
// Output: 'b1c2d3e4f5...' (actual hash)
Common hashing mistakes I fix:
- Hashing with whitespace included (match rates drop to 15-20%)
- Missing country code on phone numbers
- Not lowercasing emails before hashing
Step 3: Build the Upload Infrastructure
You have two approaches: API integration (recommended) or manual CSV uploads. API gives you automation and real-time match rate feedback.
API Integration Setup
Endpoint: https://graph.facebook.com/v19.0/{offline_event_set_id}/events
Required headers:
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ACCESS_TOKEN}`
};
Payload structure:
const payload = {
data: [
{
event_name: 'Purchase', // or 'Lead', 'CompleteRegistration', etc.
event_time: 1703894400, // Unix timestamp
user_data: {
em: ['hashed_email_1', 'hashed_email_2'], // Multiple emails if available
ph: ['hashed_phone_1'],
fn: ['hashed_first_name'],
ln: ['hashed_last_name'],
ct: ['hashed_city'],
st: ['hashed_state'],
zp: ['hashed_zip']
},
custom_data: {
currency: 'USD',
value: 99.99,
order_id: 'ORD-12345'
}
}
],
test_event_code: 'TEST12345' // Remove for production
};
Which approach should you use?
- Real-time API calls: If conversions happen immediately (e-commerce, lead forms)
- Batch uploads: If you process conversions in batches (daily/weekly CRM exports)
- Hybrid: Real-time for high-value events, batch for bulk historical data
CSV Upload Alternative
If you’re not ready for API integration, CSV uploads work but require manual intervention.
CSV format requirements:
event_name,event_time,email,phone,first_name,last_name,value,currency
Purchase,1703894400,hashed_email,hashed_phone,hashed_fn,hashed_ln,99.99,USD
Upload via Events Manager → Offline Event Set → Upload Events → Choose File.
Step 4: Deploy and Automate
For production setups, I typically deploy the upload script on Google Cloud Run or AWS Lambda with scheduled triggers.
Cloud Run deployment example:
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8080
CMD ["node", "index.js"]
Scheduled execution options:
- Google Cloud Scheduler: Cron jobs that trigger Cloud Run endpoints
- AWS EventBridge: Similar scheduling for Lambda functions
- Zapier/Make: No-code options if you’re using supported CRM connectors
Automation frequency I recommend:
- E-commerce: Every 4-6 hours
- Lead generation: Daily
- B2B high-value: Weekly batches
More frequent uploads don’t improve attribution but can hit rate limits.
Step 5: Configure Attribution Settings
In Meta Ads Manager, navigate to Attribution Settings to optimize how offline conversions impact campaign optimization.
Critical settings:
- Attribution Window: 7-day click, 1-day view (default, works for most)
- Conversion Value Optimization: Enable if you’re passing order values
- Event Priority: Set offline events to “High” if they’re more valuable than online
Which attribution window should you use?
- E-commerce: 7-day click, 1-day view
- Lead generation with long sales cycles: 28-day click, 7-day view
- B2B: 28-day click, 7-day view (longer consideration periods)
Testing & Verification
Step 1: Test Event Code
During setup, use the test_event_code parameter. This lets you send test data without affecting campaign delivery.
In Events Manager → Test Events, you’ll see:
- Event received confirmation
- Match rate for test data
- Data quality warnings
Step 2: Production Verification Remove the test event code and send real data. Check:
In Events Manager:
- Events received count (should match your upload count)
- Match rate percentage (target: 40-65%)
- Attribution data appearing in Ads Manager within 24-48 hours
In Ads Manager: Navigate to Campaigns → Columns → Customize → Offline Conversions. You should see:
- Offline conversion events attributed to campaigns
- Conversion values (if configured)
- Attribution breakdown by campaign/ad set
Cross-verification query:
-- Check your source data count vs Meta received count
SELECT
DATE(created_at) as date,
COUNT(*) as total_conversions,
SUM(value) as total_value
FROM offline_conversions
WHERE created_at >= CURRENT_DATE - 7;
Red flags that indicate problems:
- Match rates below 30% (data quality issues)
- Zero attributed conversions after 72 hours (attribution setup broken)
- Massive discrepancies between upload count and Events Manager count
Troubleshooting
Problem: Match rates below 30% Check your hashing implementation. Most low match rates come from incorrect data preprocessing. Verify phone numbers include country codes and emails are properly lowercased before hashing.
Problem: Events uploading but not attributing to campaigns
Your attribution window might be too narrow, or the event_time timestamps are outside the attribution window. Check that timestamps are recent (within 28 days) and in correct Unix format.
Problem: API calls returning 400 errors
Usually malformed JSON or incorrect event names. Meta accepts specific event names: Purchase, Lead, CompleteRegistration, AddToCart, InitiateCheckout, AddPaymentInfo, Subscribe. Custom event names won’t work for optimization.
Problem: Duplicate events skewing data
Implement deduplication using order_id or similar unique identifiers in custom_data. Meta will deduplicate based on this field plus user matching data.
Problem: Upload delays causing attribution gaps Set up monitoring for your upload jobs. I use a simple health check that alerts if uploads are more than 6 hours delayed. Attribution degrades significantly after 72 hours.
Problem: High API error rates during batch uploads You’re likely hitting rate limits. Meta allows 1000 events per API call and has hourly limits. Implement exponential backoff and split large batches into smaller chunks of 500-1000 events.
What To Do Next
Once your offline conversions are flowing, optimize your setup with Enhanced Conversions API for better match rates, or set up Server-Side Tracking for a complete first-party data infrastructure.
For lead generation specifically, check out our Lead Form to Offline Conversions guide for automated lead-to-close tracking.
Need help getting this setup right? Get a free tracking audit — I’ll review your current offline conversion configuration and identify what’s breaking your match rates.
This guide is part of the Offline Conversion Tracking Hub — complete guides for tracking offline conversions across all major ad platforms.