Gravity Forms + Meta Ads Conversion Tracking Setup
I see broken Gravity Forms → Meta Ads tracking in about 35% of the WordPress sites I audit. The usual suspects: missing event deduplication, AJAX submissions not firing the pixel, or relying purely on client-side tracking when 20-25% of your form submitters are blocking the Meta pixel entirely.
Here’s the setup that actually works — both client-side pixel and server-side CAPI with proper deduplication.
What You’ll Have Working By The End
- Every Gravity Forms submission tracked as a ‘Lead’ event in Meta Ads
- Meta CAPI sending server-side conversion data with email hashing
- Event deduplication preventing double-counting between pixel and CAPI
- Proper value tracking if you’re capturing lead value or product price in your forms
- Cross-verification between Gravity Forms entries and Meta conversions
Prerequisites
- Gravity Forms installed and active on your WordPress site
- Meta Pixel installed on your site (via GTM or direct)
- Google Tag Manager container on your site
- Meta Business Manager admin access
- Access to your WordPress admin panel
- Meta Conversions API access token (get this from Events Manager → Settings → Conversions API)
Step 1: Set Up the Gravity Forms Data Layer Push
First, we need to capture the form submission and push the data to the data layer. Add this to your active theme’s functions.php file:
// Track Gravity Forms submissions to data layer
add_action('gform_confirmation_loaded', 'push_gf_conversion_to_datalayer', 10, 2);
function push_gf_conversion_to_datalayer($confirmation, $form) {
// Generate unique event ID for deduplication
$event_id = 'gf_' . $form['id'] . '_' . time() . '_' . wp_rand(1000, 9999);
// Get user email if available
$user_email = '';
foreach ($form['fields'] as $field) {
if ($field->type == 'email') {
$user_email = rgpost('input_' . $field->id);
break;
}
}
// Get form value if you have a pricing field
$form_value = 0;
foreach ($form['fields'] as $field) {
if ($field->type == 'total' || $field->type == 'number') {
$form_value = floatval(rgpost('input_' . $field->id));
break;
}
}
?>
<script type="text/javascript">
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'gravity_form_submit',
'form_id': '<?php echo esc_js($form['id']); ?>',
'form_title': '<?php echo esc_js($form['title']); ?>',
'event_id': '<?php echo esc_js($event_id); ?>',
'user_email': '<?php echo esc_js($user_email); ?>',
'conversion_value': <?php echo $form_value; ?>,
'currency': 'USD'
});
</script>
<?php
}
This fires on every successful Gravity Forms submission and pushes the data to the data layer with a unique event ID for deduplication.
Step 2: Create the GTM Trigger
In Google Tag Manager:
- Triggers → New → Custom Event
- Event name:
gravity_form_submit - Fire this trigger when: All Custom Events
- Name it: “Gravity Forms - All Submissions”
If you want to track only specific forms, add a condition:
- This trigger fires on: Some Custom Events
- Event Condition: Form ID equals [your form ID number]
Step 3: Set Up Meta Pixel Tag in GTM
Create the client-side Meta Pixel event tag:
- Tags → New → Meta Pixel
- Configuration Tag: Your Meta Pixel base tag (or create if you haven’t)
- Event Name: Lead
- Object Properties:
- content_name:
{{Form Title}} - content_category:
form_submission - value:
{{Conversion Value}} - currency:
{{Currency}}
- content_name:
- Event ID:
{{Event ID}}(crucial for deduplication) - Trigger: Gravity Forms - All Submissions
Create these GTM variables if they don’t exist:
- Form Title: Data Layer Variable with name
form_title - Conversion Value: Data Layer Variable with name
conversion_value - Currency: Data Layer Variable with name
currency - Event ID: Data Layer Variable with name
event_id
Step 4: Configure Meta Conversions API (Server-Side)
This is where most setups fail. You need server-side tracking for the 20-25% of users blocking client-side pixels.
Add this to your functions.php file (after the data layer push code):
// Send to Meta CAPI on form submission
add_action('gform_after_submission', 'send_gf_to_meta_capi', 10, 2);
function send_gf_to_meta_capi($entry, $form) {
$access_token = 'your_meta_capi_access_token_here'; // Get from Events Manager
$pixel_id = 'your_pixel_id_here';
// Generate same event ID as client-side for deduplication
$event_id = 'gf_' . $form['id'] . '_' . $entry['date_created'] . '_' . $entry['id'];
// Get user data
$user_email = '';
$user_phone = '';
$first_name = '';
$last_name = '';
foreach ($form['fields'] as $field) {
$field_id = $field->id;
switch ($field->type) {
case 'email':
$user_email = rgar($entry, $field_id);
break;
case 'phone':
$user_phone = rgar($entry, $field_id);
break;
case 'name':
if (isset($field->inputs)) {
$first_name = rgar($entry, $field_id . '.3');
$last_name = rgar($entry, $field_id . '.6');
} else {
$full_name = rgar($entry, $field_id);
$name_parts = explode(' ', $full_name);
$first_name = $name_parts[0];
$last_name = isset($name_parts[1]) ? $name_parts[1] : '';
}
break;
}
}
// Get conversion value
$value = 0;
foreach ($form['fields'] as $field) {
if ($field->type == 'total' || $field->type == 'number') {
$value = floatval(rgar($entry, $field->id));
break;
}
}
// Prepare event data
$event_data = array(
'data' => array(
array(
'event_name' => 'Lead',
'event_time' => time(),
'event_id' => $event_id,
'action_source' => 'website',
'event_source_url' => get_permalink(get_the_ID()),
'user_data' => array(),
'custom_data' => array(
'content_name' => $form['title'],
'content_category' => 'form_submission',
'value' => $value,
'currency' => 'USD'
)
)
)
);
// Add hashed user data
if (!empty($user_email)) {
$event_data['data'][0]['user_data']['em'] = array(hash('sha256', strtolower(trim($user_email))));
}
if (!empty($user_phone)) {
$clean_phone = preg_replace('/[^0-9]/', '', $user_phone);
$event_data['data'][0]['user_data']['ph'] = array(hash('sha256', $clean_phone));
}
if (!empty($first_name)) {
$event_data['data'][0]['user_data']['fn'] = array(hash('sha256', strtolower(trim($first_name))));
}
if (!empty($last_name)) {
$event_data['data'][0]['user_data']['ln'] = array(hash('sha256', strtolower(trim($last_name))));
}
// Send to Meta CAPI
$response = wp_remote_post("https://graph.facebook.com/v21.0/{$pixel_id}/events", array(
'headers' => array(
'Content-Type' => 'application/json',
),
'body' => json_encode($event_data),
'timeout' => 30
));
// Log errors for debugging
if (is_wp_error($response)) {
error_log('Meta CAPI Error: ' . $response->get_error_message());
}
}
Replace your_meta_capi_access_token_here and your_pixel_id_here with your actual values.
Step 5: Set Up Meta Custom Conversion
In Meta Business Manager:
- Events Manager → Custom Conversions → Create
- Event: Lead
- URL Contains: Your form page URL (optional but recommended)
- Conversion Name: “Gravity Forms - [Form Name]”
- Category: Lead
- Default Value: Set if you have consistent lead values
If you’re tracking multiple forms, create separate custom conversions for each with specific URL conditions.
Step 6: Testing & Verification
Test the Client-Side Pixel
- Events Manager → Test Events
- Enter your website URL
- Submit a test form
- Look for the ‘Lead’ event with your event_id
- Verify custom data shows your form title and any values
Test Server-Side CAPI
- Check the same Test Events tool
- You should see duplicate events (pixel + CAPI) with the same event_id
- Meta automatically deduplicates these — you’ll see both sources but only count once
- CAPI events show “Server” as the connection method
Cross-Check the Numbers
Every 24-48 hours, compare:
- Gravity Forms entries: WordPress Admin → Forms → Entries
- Meta Ads conversions: Ads Manager → Columns → Conversions
Acceptable variance is 5-15%. If you’re seeing 20%+ variance, something’s broken.
Troubleshooting
Problem: No events showing in Meta Test Events tool
Check your pixel ID and access token. Verify the Gravity Forms data layer push is firing by checking browser console. Look for JavaScript errors preventing the GTM tag from firing.
Problem: Client-side events working but CAPI events missing
Your CAPI access token is likely invalid or your pixel ID is wrong. Check your error logs — WordPress will log failed API calls. Verify the gform_after_submission hook is firing.
Problem: Events showing but no conversions in Meta Ads
Your custom conversion setup is probably too restrictive. Remove URL conditions and test with just the event name ‘Lead’. Make sure your ad account has conversion tracking enabled.
Problem: Duplicate conversions (counting both pixel and CAPI)
Your event IDs don’t match between client-side and server-side. The deduplication logic requires identical event_id values. Double-check the ID generation in both code snippets.
Problem: Only getting conversions from some form submissions
AJAX forms can fire before the pixel loads. Add a small delay before form submission or ensure your pixel loads in the document head, not footer. For server-side, check that the gform_after_submission hook isn’t being blocked by other plugins.
Problem: Conversion values not tracking correctly
Gravity Forms field IDs can be tricky. Use the form preview to inspect the actual input names. Total fields use input_X format, but complex fields might use input_X.Y format for sub-fields.
What To Do Next
- Set up Gravity Forms Google Ads conversion tracking for cross-platform attribution
- Configure Gravity Forms GA4 tracking for comprehensive analytics
- Explore Gravity Forms to HubSpot integration for CRM automation
- Get a free tracking audit to verify your setup is working correctly
This guide is part of the Meta Ads Conversion Tracking Hub — comprehensive guides for tracking any conversion event in Meta Ads.