WPForms submission not creating HubSpot contact using Contacts API

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:

  1. Is /crm/v3/objects/contacts/batch/upsert supported with a HubSpot service key/private app having crm.objects.contacts.write scope?
  2. Is any additional scope required?
  3. Is email supported as idProperty for contact batch upsert?
  4. Is there anything else required for API-created contacts when Non-HubSpot Forms collection is OFF?
  5. 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:

  1. 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.

  2. 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.

  3. 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).

  4. 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.

  5. 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.