HubSpot Forms API fix

Send HubSpot Forms API data from WordPress with `wp_remote_post()`.

Most WordPress HubSpot 400 errors come from sending form-encoded data instead of JSON, missing the JSON content type, or not checking the HubSpot response body.

Fast checklist

  • Build the HubSpot endpoint with portal ID and form GUID.
  • Encode the payload with `wp_json_encode()`.
  • Send `Content-Type: application/json`.
  • Check `is_wp_error()` before reading the response.
  • Log HubSpot's response body when debugging 400 errors.

Baseline WordPress example

Use `wp_remote_post()` with a JSON string body. WordPress accepts request options like `headers`, `body`, and `timeout`, and returns either a response array or `WP_Error`.

$portal_id = '12345678';
$form_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';

$endpoint = sprintf(
    'https://api.hsforms.com/submissions/v3/integration/submit/%s/%s',
    rawurlencode($portal_id),
    rawurlencode($form_id)
);

$payload = [
    'fields' => [
        [
            'name' => 'email',
            'value' => 'developer@example.com',
        ],
        [
            'name' => 'firstname',
            'value' => 'Alex',
        ],
    ],
    'context' => [
        'pageUri' => home_url('/contact/'),
        'pageName' => 'Contact',
    ],
];

if (!empty($_COOKIE['hubspotutk'])) {
    $payload['context']['hutk'] = sanitize_text_field(wp_unslash($_COOKIE['hubspotutk']));
}

$response = wp_remote_post($endpoint, [
    'timeout' => 20,
    'headers' => [
        'Content-Type' => 'application/json',
    ],
    'body' => wp_json_encode($payload),
]);

if (is_wp_error($response)) {
    error_log('HubSpot request failed: ' . $response->get_error_message());
    return;
}

$status_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);

if ($status_code >= 400) {
    error_log('HubSpot returned ' . $status_code . ': ' . $body);
}

Common mistakes

1. Sending an array body instead of JSON

WordPress can send array bodies, but HubSpot's v3 Forms API expects JSON for this submission endpoint. Encode the payload and set the content type.

$response = wp_remote_post($endpoint, [
    'body' => $payload,
]);

2. Missing `Content-Type: application/json`

If the body is JSON but the header is missing, HubSpot may not parse the request the way you expect.

3. Not reading the response body

A 400 status alone is not enough. HubSpot's response body often points to the malformed field, missing consent block, bad context value, or invalid payload shape.

4. Hard-coding `hutk`

`hutk` should come from the visitor's `hubspotutk` cookie when available. Do not reuse one visitor's cookie across all submissions.

Adding legal consent

If your HubSpot form requires processing consent, add `legalConsentOptions` only when your WordPress form actually collected that consent.

if (!empty($_POST['privacy_consent'])) {
    $payload['legalConsentOptions'] = [
        'consent' => [
            'consentToProcess' => true,
            'text' => 'I agree to allow this website to store and process my personal data.',
        ],
    ];
}

Debugging flow

Log the payload before sending it, paste that same JSON into the debugger, then compare the debugger warnings with the HubSpot response body.

$json_payload = wp_json_encode($payload);
error_log('HubSpot payload: ' . $json_payload);

$response = wp_remote_post($endpoint, [
    'timeout' => 20,
    'headers' => [
        'Content-Type' => 'application/json',
    ],
    'body' => $json_payload,
]);