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,
]);