A WordPress site owner posted on the HubSpot community forum with a mystery. They had not set up any integration between Contact Form 7 and HubSpot. The only HubSpot code on their site was the chat widget script in the footer. Yet HubSpot was somehow collecting CF7 form submissions and creating entries labelled .wpcf7-form, .submitting. They had deleted the integration three times and it kept coming back.
The HubSpot community moderator explained what was happening: a HubSpot feature called Non-HubSpot Forms was enabled on their account. This feature runs a script that scans every page on your site where the HubSpot tracking code is installed and automatically captures any form submission it detects, including CF7 forms. No setup required on your end. HubSpot just takes it.
That sounds convenient until you look at what it actually captures.
What Non-HubSpot Forms Captures (And What It Misses)
When HubSpot's tracking script auto-captures a CF7 submission, it takes what it can see from the page: the form field values that were submitted. But it has no knowledge of your CF7 form's structure, your field naming conventions, or which fields map to which HubSpot contact properties.
What HubSpot receives looks something like this — a flat, unstructured set of values with no meaningful property mapping. The contact gets created in HubSpot but with fields that do not align to your HubSpot contact properties, no lifecycle stage set, no list assignment, no tagging, and no automation triggered.
You get a contact record that exists but is essentially useless for any workflow, pipeline, or follow-up automation you have set up in HubSpot.
How to Stop the Ghost Captures
Disabling Non-HubSpot Forms stops HubSpot's tracking script from auto-capturing CF7 submissions.
In your HubSpot account, go to Marketing, then Forms, then click the Non-HubSpot Forms option in the left sidebar. Toggle off "Collect data from external forms." Save.
Once disabled, the ghost entries stop appearing. The existing ones will remain in HubSpot but no new ones will be created from CF7 submissions.
Now you have a clean slate. The question is what to replace it with.
The Right Way to Send CF7 Data to HubSpot
Instead of letting HubSpot's tracking script grab whatever it can see, send exactly what you want HubSpot to receive with full control over how it maps to your contact properties.
HubSpot's Contacts API accepts a POST request with a structured payload where you specify exactly which HubSpot properties receive which values from your CF7 form:
{
"properties": {
"email": "jane@example.com",
"firstname": "Jane",
"lastname": "Smith",
"phone": "1234567890",
"company": "Acme Corp",
"lifecyclestage": "lead",
"hs_lead_status": "NEW"
}
}
This creates a proper HubSpot contact with the right properties populated, the right lifecycle stage set, and the right conditions to trigger your HubSpot workflows and automations.
Contact Form to API handles this from the WordPress dashboard. You set the HubSpot Contacts API endpoint, add your HubSpot Private App token as a Bearer header, and map each CF7 field to the correct HubSpot property name. Every form submission creates a properly structured contact in HubSpot with the fields you defined, not a ghost entry with unrecognised field labels.
Why This Is Better Than the Tracking Script Approach
The Non-HubSpot Forms feature exists for convenience. It is designed for people who do not want to set up a proper integration and are happy with approximate data. For a business where HubSpot is the actual CRM driving sales and follow-up, approximate data creates noise, not value.
When you send CF7 data through a direct API integration, you control exactly which contact properties are set, which lifecycle stage the contact enters, and which workflows trigger. You can set hs_lead_status to NEW so the lead appears in your sales queue immediately. You can set lifecyclestage to lead or marketingqualifiedlead depending on which form they submitted. You can pass UTM parameters, page URLs, or custom field values that the tracking script cannot capture.
The ghost entries from Non-HubSpot Forms will never give you that level of control. They are a passive capture mechanism. A direct API integration is an active, intentional one.
Setting Up the HubSpot Private App Token
HubSpot moved from legacy API keys to Private App tokens. To call the Contacts API from your WordPress site, you need a Private App token.
In HubSpot, go to Settings, then Integrations, then Private Apps. Create a new private app and give it the crm.objects.contacts.write scope at minimum. Copy the generated token. This is what goes in the Authorization header as Bearer YOUR_TOKEN.
The HubSpot Contacts API endpoint to create or update a contact is:
POST https://api.hubapi.com/crm/v3/objects/contacts
For updating an existing contact by email (upsert behaviour), use:
PATCH https://api.hubapi.com/crm/v3/objects/contacts/{contactId}
Or use the search endpoint to find a contact by email first, then update if found.
Summary
Non-HubSpot Forms is why HubSpot captures CF7 submissions without any integration being set up. The data it captures is shallow and unstructured. Disable it under Marketing > Forms > Non-HubSpot Forms to stop the ghost entries.
Then replace it with a direct API integration that sends structured, mapped data to HubSpot's Contacts API. That gives you real contact records with the right properties, lifecycle stages, and workflow triggers — which is what HubSpot is actually built for.
Top comments (1)
I was intrigued by the Non-HubSpot Forms feature and how it can lead to unstructured data in HubSpot, as you've pointed out with the example of CF7 form submissions being captured as a flat set of values without meaningful property mapping. Disabling this feature and using the HubSpot Contacts API to send structured data is definitely a more reliable approach, especially when you need to trigger specific workflows and automations. The example payload you provided shows how this can be done with precision, mapping CF7 form fields to specific HubSpot properties like
email,firstname, andlifecyclestage. Have you found any other benefits to using a direct API integration over the tracking script approach, such as improved data consistency or reduced errors?