Meta Ads, Conversions API, CAPI, Offline Conversions, Lead Generation, Server-Side Tracking
Meta Conversions API for Offline Lead Tracking: 2026 Setup Guide
In 2018, Meta introduced a separate Offline Conversions API , a tool specifically designed to send in-store purchases, phone orders, and CRM events back to the ad platform without a browser involved. It worked, and many agencies built integrations around it.
On May 14, 2025, Meta permanently shut it down.
Everything that used to flow through the Offline Conversions API now flows through the unified Conversions API (CAPI) , the same endpoint used for online events like web purchases and form submissions. If your integration relied on the old API, you either have a silent data gap right now or you have already been forced to rebuild.
This guide covers the current state of offline conversion tracking through Meta CAPI: why it matters, how to set it up from scratch, and the specific integration patterns Metaphase uses with real clients across e-commerce, healthcare, SaaS, and membership businesses.
Key Takeaways
Meta's standalone Offline Conversions API was permanently discontinued May 14, 2025. All offline events now use the Conversions API (CAPI).
Browser pixels miss 30–60% of actual conversions due to iOS privacy changes, ad blockers, and cross-device journeys. CAPI operates server-side and is unaffected by these restrictions.
Running both pixel and CAPI simultaneously , with proper deduplication via event_id , gives you the highest match rate and conversion coverage.
Capturing the fbclid parameter when a user clicks your Meta ad is critical for attribution accuracy. Store it in a cookie and pass it through your CRM.
The more user data fields you send (email, phone, name, IP, user agent), the higher your match quality score and the better Meta's algorithm can optimize.
Why the Browser Pixel Is No Longer Enough
The Meta pixel is still a useful tool. But in 2025, relying on it alone is like navigating with a map that has 30–60% of the roads missing.
Here is what the pixel cannot see:
iOS device events: Apple's App Tracking Transparency (ATT) framework requires users to opt in to tracking. The majority of iOS users have opted out, which means the pixel silently fails to fire , or fires without identity data that would allow attribution , across a large portion of iPhone traffic.
Ad-blocked sessions: An estimated 40%+ of desktop users run ad blockers. Many of these block pixel scripts from loading at all. No script, no event, no attribution.
Safari ITP: Safari's Intelligent Tracking Prevention caps third-party cookie lifetimes at 7 days, sometimes as low as 1 day. Cross-session attribution breaks for returning visitors on Safari.
Cross-device journeys: Someone clicks your Meta ad on their phone, then converts on their laptop later. The pixel sees two different people. CAPI can match both events back to the original ad click using hashed identity data.
Offline events: A phone call. An in-office appointment. A signed contract. A membership activation. There is no page load, no pixel fire. These events are completely invisible to browser-side tracking.
CAPI bypasses all of these problems. It fires from your server, not the user's browser. It cannot be blocked by ad blockers. iOS privacy settings do not affect it. It can send events that happen hours, days, or weeks after the original ad click , even events that never had a browser session at all.
Pixel vs. CAPI vs. Both
Setup
Accuracy
Setup Complexity
Best For
Pixel only
Low (30–60% data loss)
Easy
Starter campaigns only
CAPI only
High (no browser dependency)
Medium
Server-based or offline-heavy flows
Pixel + CAPI (redundant)
Highest (with deduplication)
Medium-High
All serious campaigns
The Metaphase standard is always to run both, always to deduplicate. The pixel provides real-time signals and gives you the event ID needed for deduplication. CAPI provides reliability, server-side accuracy, and the ability to send downstream events that the pixel would never see.
How CAPI Works: The Full Data Flow
Here is what happens from the moment a user sees your Meta ad to the moment CAPI fires:
User sees your Meta ad on Facebook or Instagram.
User clicks the ad. Meta appends a ?fbclid=XXXXXXX parameter to your destination URL.
User lands on your website. Your browser pixel fires a PageView event. Your JavaScript captures the fbclid and stores it in a cookie.
User fills out a form, makes a purchase, or books an appointment. The browser pixel fires a lead or purchase event in real time.
Your CRM or billing system records the conversion event with all associated data , email, phone, the stored fbclid.
Your server fires a CAPI event to Meta's endpoint with the hashed user data, the event name, a unique event_id, and the fbclid.
Meta matches the CAPI event to the original ad click using the fbclid and hashed identity data. The algorithm learns from this real conversion signal.
For offline events , like a membership activation that happens in your booking software two weeks after the original click , steps four and five are replaced by your CRM or booking system triggering the CAPI fire directly, with no browser component at all.
Customer Matching Parameters
The quality of your CAPI integration is directly proportional to how much user data you send. Meta uses these fields to match your server-side event back to a specific user and their ad click history. The more fields you include, the higher your match quality score.
Parameter
Field Name
Hashing
Priority
Email
em
SHA-256, lowercase
Critical
Phone
ph
SHA-256, digits only
Critical
First name
fn
SHA-256, lowercase
High
Last name
ln
SHA-256, lowercase
High
ZIP code
zp
SHA-256, no spaces
Medium
City
ct
SHA-256, lowercase, no spaces
Medium
State
st
SHA-256, 2-letter code
Medium
Client IP address
client_ip_address
Plain text
High
User agent
client_user_agent
Plain text
High
fbc (click ID)
fbc
Plain text
High
fbp (browser ID)
fbp
Plain text
Medium
Always send as many parameters as you have. For offline events where you may not have the IP address or user agent (because there was no browser session), focus on sending email, phone, first name, last name, and the fbc value if it was captured at the original landing.
Step 1: Get Your Access Token
Go to Meta Business Manager → Events Manager.
Select your Pixel.
Click Settings.
Under "Conversions API," click Generate access token.
Store this token as an environment variable on your server. Do not hardcode it in your source code or commit it to a repository.
Your Pixel ID is listed on the same settings page. You will need both the Pixel ID and the access token for every CAPI request.
Step 2: Set Up Deduplication
This is the most commonly skipped step, and skipping it causes real problems. If both your browser pixel and your CAPI integration fire for the same conversion event, Meta will count it twice , inflating your reported conversions and distorting your algorithm's signals.
The fix is simple: pass the same event_id in both the browser pixel call and the CAPI payload. Meta deduplicates automatically when it receives two events with the same event_id within a 48-hour window.
Browser pixel side:
fbq('track','Purchase',{value:149.00,currency:'USD',},{eventID:'order_12345'// must match the CAPI event_id below});
fbq('track','Purchase',{value:149.00,currency:'USD',},{eventID:'order_12345'// must match the CAPI event_id below});
fbq('track','Purchase',{value:149.00,currency:'USD',},{eventID:'order_12345'// must match the CAPI event_id below});
The event_id should be a stable, unique identifier tied to the specific transaction , an order ID, booking ID, or a UUID generated at conversion time. Never use a timestamp alone as the event ID.
Step 3: Capture the fbclid
When a user clicks your Meta ad, the fbclid parameter is appended to your URL. You need to capture it immediately when the user lands on your page and store it for later use when CAPI fires.
functiongetCookieValue(name){constmatch = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');returnmatch ? match.pop() : '';}functionstoreFbclid(){consturlParams = newURLSearchParams(window.location.search);constfbclid = urlParams.get('fbclid');if(fbclid){// Store for 90 daysdocument.cookie = `fbclid=${fbclid}; max-age=${90*24*60*60}; path=/; SameSite=Lax`;}}storeFbclid();
functiongetCookieValue(name){constmatch = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');returnmatch ? match.pop() : '';}functionstoreFbclid(){consturlParams = newURLSearchParams(window.location.search);constfbclid = urlParams.get('fbclid');if(fbclid){// Store for 90 daysdocument.cookie = `fbclid=${fbclid}; max-age=${90*24*60*60}; path=/; SameSite=Lax`;}}storeFbclid();
functiongetCookieValue(name){constmatch = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');returnmatch ? match.pop() : '';}functionstoreFbclid(){consturlParams = newURLSearchParams(window.location.search);constfbclid = urlParams.get('fbclid');if(fbclid){// Store for 90 daysdocument.cookie = `fbclid=${fbclid}; max-age=${90*24*60*60}; path=/; SameSite=Lax`;}}storeFbclid();
When the user submits a form, pull the stored fbclid from the cookie and store it in your CRM against the lead record. When CAPI fires later, format it as fbc: fb.1.{unix_timestamp_ms}.{fbclid}.
Also capture the _fbp cookie value, which is set by the Meta pixel on page load. This provides a secondary matching signal when the fbclid is not available.
Step 4: Build the User Data Object
Here is a production-ready Python function for building the user_data dictionary with proper hashing:
Use the standard Meta event names wherever possible. They are pre-configured for optimization objectives and work automatically with Meta's algorithm signals.
Event Name
When to Fire
Key Custom Data Fields
Lead
Form submission, consultation request, call booked
lead_id, lead_type
CompleteRegistration
Account created, patient registration
registration_id
Schedule
Appointment booked
appointment_id, service_type
Purchase
Payment confirmed, membership activated
value, currency, order_id
StartTrial
Free trial started
trial_id, plan_type
Subscribe
Recurring subscription activated
subscription_id, plan_type
Contact
Phone call, live chat, direct inquiry
contact_method
For events that do not map cleanly to a standard event name, use a custom event name (for example, TreatmentCompleted or ContractSigned) and then create a Custom Conversion in Meta Events Manager to track and optimize for it.
Real Integration Patterns
Pattern 1: Stripe Webhook → CAPI Purchase
This is the pattern Metaphase uses for SaaS clients and e-commerce accounts. When a Stripe payment completes, Stripe fires a checkout.session.completed webhook to your server endpoint. Your handler then fires CAPI with the Stripe session ID as the event_id.
For B2B clients using HubSpot, a cron job runs every two minutes, queries HubSpot for new deals created since the last run, and fires a CAPI Lead event for each one. This is the pattern Metaphase uses for its own client acquisition campaigns.
def sync_hubspot_leads():deals = get_new_hubspot_deals(since=last_run_timestamp)fordealindeals:fire_meta_capi(event_name="Lead",event_id=f"hs_deal_{deal['id']}", # HubSpot deal IDas dedup keyemail=deal.get("email",""),value=0,currency="USD")
def sync_hubspot_leads():deals = get_new_hubspot_deals(since=last_run_timestamp)fordealindeals:fire_meta_capi(event_name="Lead",event_id=f"hs_deal_{deal['id']}", # HubSpot deal IDas dedup keyemail=deal.get("email",""),value=0,currency="USD")
def sync_hubspot_leads():deals = get_new_hubspot_deals(since=last_run_timestamp)fordealindeals:fire_meta_capi(event_name="Lead",event_id=f"hs_deal_{deal['id']}", # HubSpot deal IDas dedup keyemail=deal.get("email",""),value=0,currency="USD")
Pattern 3: Booking Software → CAPI Subscribe
For med spa and membership businesses, the real conversion is not a form fill , it is a membership activation in the booking software. Here is how Metaphase wires Boulevard to CAPI, including location-level segmentation via custom data:
In Meta Events Manager, you then create a custom conversion: Purchase where content_type contains "membership." This gives you the ability to optimize individual ad sets toward location-specific membership signups and build lookalike audiences from actual members rather than all website visitors.
Custom Conversions for Segmentation
Standard events like Purchase are broad. If your business has multiple product lines, locations, or customer segments, custom conversions let you create optimizable signals at a much more granular level.
To create a custom conversion:
In Meta Events Manager, go to Custom Conversions → Create.
Select the base event (e.g., Purchase).
Add a rule: content_type contains membership.
Name it descriptively: "New Member , Dallas."
Repeat for each variant (Southlake, online, etc.).
This allows you to optimize separate ad sets toward each segment, see true cost per acquisition by product or location, and build segment-specific lookalike audiences.
Step 6: Validate with the Test Events Tool
Before going live with real traffic, validate your integration using Meta's Test Events tool:
Go to Events Manager → Your Pixel → Test Events.
Click Test Server Events.
Send a test payload from your server to the CAPI endpoint.
Confirm the event appears in the Test Events panel within 60 seconds.
Check the Match quality score , aim for 7 or higher out of 10.
Also send a simultaneous pixel event with the same event_id to verify deduplication is working. You should see exactly one event in the Test Events panel, not two.
Use the Payload Helper tool in Events Manager to verify your hashing format before sending live traffic. Incorrect hashing is the most common cause of poor match quality scores.
Troubleshooting
Events not appearing in Events Manager:
Check that your access token is valid and has not expired.
Verify the Pixel ID in your API URL is correct.
Confirm event_time is a Unix timestamp within the last 7 days.
Match quality score is Poor:
Add more user data fields , phone and name in addition to email make a significant difference.
Confirm phone number is digits-only before hashing (no dashes, no parentheses, no country code).
Confirm email is lowercase before hashing.
Check that you are including fbc when it is available.
Duplicate events appearing:
Verify the event_id is identical in both the pixel call and the CAPI payload.
Check whether you are sending batched events more than 48 hours after the original browser event , outside the dedup window, duplicates will not be caught automatically.
CAPI fires but conversions are not attributed:
Check that action_source is set to "website" for web-originated events and "system_generated" for events that originated in a CRM or server-only context with no browser session.
Verify that your attribution window settings in Meta Ads Manager match your customer journey length. If your customers typically convert 10 days after clicking, a 7-day click window will miss them.
Expected Impact
Based on Metaphase client results and industry benchmarks:
Metric
Pixel Only
Pixel + CAPI
Attributed conversions
40–70% of actual
85–95% of actual
Match quality
Low-Medium
High-Excellent
CPL over 90 days
Baseline
15–35% lower
ROAS over 90 days
Baseline
20–40% higher
The improvement compounds. Meta's algorithm improves as it receives cleaner signals. Accounts with 6+ months of CAPI data consistently outperform pixel-only accounts , not because the ads are better, but because the algorithm knows what a real buyer looks like.
Need Help Wiring This Up?
Metaphase builds the server-side infrastructure that connects your Meta campaigns to real revenue. We typically get Tier 2 implementations live in 3–5 business days , CAPI wired, deduplication verified, match quality confirmed.