Hi HubSpot Community,
I am integrating a WordPress WPForms contact form with HubSpot using the HubSpot Contacts API.
Setup:
- WordPress with WPForms
- Form ID: 212
- HubSpot service key/private app authentication
- Scope:
crm.objects.contacts.write
- Endpoint:
/crm/v3/objects/contacts/batch/upsert
- Email is being used as the
idProperty
Previously, HubSpot’s Collect data from website forms (Non-HubSpot Forms) feature was ON, and submissions were being captured by HubSpot.
I turned Collect data from website forms OFF because I want the WordPress/PHP API integration to create/update contacts directly.
The WPForms submission itself is successful and redirects to the Thank You page, but:
- The new contact does not appear in HubSpot Contacts.
- I do not see the expected API call in HubSpot Developer Monitoring → API Calls.
- The WPForms entry is saved successfully in WordPress.
The PHP integration is triggered using:
wpforms_process_complete
and sends the contact using wp_remote_post() with:
Authorization: Bearer [SERVICE KEY]
Could you please confirm:
- Is
/crm/v3/objects/contacts/batch/upsert supported with a HubSpot service key/private app having crm.objects.contacts.write scope?
- Is any additional scope required?
- Is
email supported as idProperty for contact batch upsert?
- Is there anything else required for API-created contacts when Non-HubSpot Forms collection is OFF?
- Why might no API call appear in Developer Monitoring?
Thank you.
I just tested it with a new service key that has only crm.objects.contacts.write and it worked fine.
Are you saying that the API log shows no activity? If there’s at least a post request, it should at least show an error if the payload is incorrect.
Can you check if WP is executing the wp_remote_post? Maybe your webserver is blocking the request?
Can you show us the payload? You’re using a batch endpoint so you need to format the payload in a batch format.
Here is an example payload with 2 entries. Since you’re doing it on a submission by submission basis, you would only need one entry in the array.
{
"inputs": [
{
"idProperty": "email",
"id": "EMAIL GOES HERE",
"properties": {
"firstname": "Michael",
"lastname": "Ma"
}
},
{
"idProperty": "email",
"id": "EMAIL GOES HERE",
"properties": {
"firstname": "Michael",
"lastname": "Ma"
}
}
]
}
Turning Non-HubSpot Forms OFF should not block API creates. If Monitoring shows no call at all, the request is usually not leaving WordPress cleanly (hook, URL, auth, or a silent wp_remote_post failure), before HubSpot scope rules even matter.
Checklist that usually finds it:
-
Log the raw response in PHP. In the wpforms_process_complete handler, capture $response = wp_remote_post(...), then log wp_remote_retrieve_response_code() and wp_remote_retrieve_body(). If you never see a 200/201/207, HubSpot never got a good upsert. Many “no Monitoring row” cases are 401/403/404 returned to WordPress and ignored.
-
Scopes for upsert. crm.objects.contacts.write alone is often not enough for upsert. Add crm.objects.contacts.read (upsert must look up by idProperty). Reinstall / rotate the private app token after changing scopes; old Bearer tokens keep the old grant.
-
Endpoint + payload shape. POST https://api.hubapi.com/crm/v3/objects/contacts/batch/upsert with JSON body like:
{"inputs":[{"idProperty":"email","id":"person@example.com","properties":{"email":"person@example.com","firstname":"..."}}]}
Email as idProperty is supported for contacts. Content-Type must be application/json. Confirm the host is api.hubapi.com (or the correct EU hub host if that portal requires it).
-
Isolate with one simple create. From the same PHP path, try a single POST /crm/v3/objects/contacts (not batch upsert) with email + name. If that lands in Contacts and in Monitoring, your auth works and the bug is batch payload / idProperty wiring. If that also never appears, the hook is not firing or the token/portal is wrong.
-
Non-HubSpot Forms OFF is fine. That only stops cookie/script capture of HTML forms. It does not gate Private App CRM writes. API-created contacts do not need that feature.
If you can paste the HTTP status + response body from wp_remote_post (redact the token), the next step is usually obvious: auth/scope vs payload vs hook not running.
VK | AXIOPRAX
The biggest clue here is that nothing shows up in HubSpot API monitoring.
I’d debug this from the WordPress side first rather than changing scopes yet. Inside “wpforms_process_complete”, log right before “wp_remote_post()”, then log the response status/body or the full “WP_Error” if it fails.
That will tell you whether the hook is actually reaching that code and whether WordPress is sending the request at all.
I’d also test the exact same payload/token outside WPForms with curl or Postman. If that reaches HubSpot, you’ve isolated the problem to the WordPress execution path rather than the HubSpot API.
Turning off Non-HubSpot Forms shouldn’t prevent contacts created through the API — those are separate paths.
Right now I’d first prove: hook fired → request sent → HTTP response received. Then debug the HubSpot payload/auth only if the request is actually getting there.