# Tools Gated Forms – Troubleshooting

**Last Updated:** 2026-02-02

Guide for diagnosing and fixing tools gated form failures (e.g. "Es gab einen Fehler beim Senden").

## Log location and how to read

- **Log file:** `v2/logs/tools_hubspot.log`
- **Path from repo root:** `v2/logs/tools_hubspot.log`
- **Writable:** The API ensures the `v2/logs/` directory exists on first request; it must be writable by the web server user. If the directory is missing or not writable, the API logs a warning to PHP `error_log` and submissions may still succeed but HubSpot/request details will not be written to the file.

**How to read:**

- **Local:** `tail -f v2/logs/tools_hubspot.log` or open the file in an editor.
- **Production:** Use your hosting log viewer or SSH and `tail`/`cat` the same path.
- **What you’ll see:** Each line is timestamped. Look for:
  - `Full API input:` – JSON payload received (email, name, tool_name, etc.).
  - `Filtered N null/empty fields from HubSpot payload` – optional fields with null/empty values were removed before sending to HubSpot.
  - `Request validation failed` – validation errors (missing/invalid email or name).
  - `HubSpot Request - Tool:` – HubSpot URL and tool name.
  - `HubSpot Response - Tool: ... HTTP Code:` – HTTP status from HubSpot.
  - `HubSpot Error:` or `HubSpot Tools Form failed response:` – HubSpot error body when submission fails.

## Test script and curl

Use the test script or curl to verify the API without the browser.

**Test script:** `v2/scripts/dev-helpers/test-collect-lead.php`

- From repo root: `php v2/scripts/dev-helpers/test-collect-lead.php [base_url]`
- Example: `php v2/scripts/dev-helpers/test-collect-lead.php http://localhost:8003`
- To test validation (400): `php v2/scripts/dev-helpers/test-collect-lead.php http://localhost:8003 invalid`
- Script prints HTTP status and response body; exit 0 on expected outcome (200 + success, or 400 for invalid, or 502/503 with success false).

**curl (minimal valid payload):**

```bash
curl -s -w "\nHTTP_CODE:%{http_code}\n" -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","name":"Test User","tool_name":"Stundenlohnrechner"}' \
  "https://your-domain.com/v2/api/collect-lead.php"
```

- **200 + `"success":true`** – API and HubSpot submission succeeded.
- **400** – Validation failed (e.g. invalid email, missing name); response body includes `errors` or `validation_errors`.
- **502 or 503** – HubSpot submission failed or token/config unavailable; response body includes `success: false` and `message`.

## Browser / Network tab

1. Open DevTools → Network.
2. Submit the gated form (name + email, click Entsperren).
3. Find the request to `collect-lead.php`.
4. Check:
   - **Status:** 200 (success or API-reported failure), 400 (validation), 502/503 (HubSpot/config failure), 5xx (server error).
   - **Response body:** JSON with `success` (true/false), optional `message`, `errors` or `validation_errors`.
5. If status is 200 but `success` is false, the user should see an error message (frontend must check `response.body.success`).

## Required fields: name and email

**All tools that gate export or content must send both `name` and `email` to `collect-lead.php`.** The API requires both; sending only `email` causes **400 Validation failed**. If a tool currently collects only email, add a name field and include `name` in the JSON body. See [GATED_EXPORT_MODAL_SPEC.md](./GATED_EXPORT_MODAL_SPEC.md) for the canonical export modal pattern.

## Common causes

| Cause | HTTP status | What to check |
|-------|-------------|----------------|
| **Validation** | 400 | Log: "Request validation failed". Ensure payload has valid `email` and `name` (both required). Missing `name` causes 400. |
| **Missing token** | 503 | Production: set `HUBSPOT_API_TOKEN` in environment. See `v2/config/hubspot-config.php`. |
| **HubSpot failure** | 502 or 503 | Log: "HubSpot Tools Form submission failed" and HTTP code from HubSpot. Check form GUID, token, and payload. API now returns 502 when HubSpot fails so frontend gets `response.ok === false`. |
| **Network / CORS** | (no response or failed fetch) | User sees generic error. Check console for CORS or network errors; ensure endpoint is correct (e.g. `/v2/api/collect-lead.php`). |
| **PHP fatal** | 500 | Response may be HTML error page. Check PHP error logs and that all required includes and config are present. |

## HubSpot Forms API v3 field filtering

HubSpot Forms API v3 (since March 2022) rejects payloads that include fields with **null** or **empty string** values for optional fields. The error message is often: `Cannot build FormSubmissionValue, some of required attributes are not set [value]`.

**What the API does:** `collect-lead.php` filters the payload before sending to HubSpot:

- **Required fields** (email, firstname, lastname) are always sent.
- **Optional fields** with `null` or `""` are removed from the payload.
- Boolean `false` is valid and is not filtered.

**In the log:** Look for `Filtered N null/empty fields from HubSpot payload` to confirm filtering ran. The next log line (`HubSpot Data:`) shows the payload actually sent (no null/empty optional fields).

**If you add new fields:** Only send fields that exist on the HubSpot form. Do not send `null` or `""` for optional fields; omit them instead. In PHP, filter the `fields` array before calling the HubSpot API (see `v2/api/collect-lead.php` for the pattern).

## Alpine.js and frontend errors

If you see `Cannot read properties of undefined (reading '$data')` on a tools page, the cause is usually a script that accesses Alpine.js (`element.__x.$data`) before Alpine has initialized or when no `[x-data]` element exists. Fix by:

- Checking that the element exists and has `__x` and `__x.$data` before accessing them.
- Using a short retry loop (e.g. 100 ms, max 10 attempts) instead of a single delayed access.
- Not assuming Alpine is ready immediately after `DOMContentLoaded`; allow 1+ second or poll until `__x.$data` is present.

## API behavior on HubSpot failure

When HubSpot submission fails, the collect-lead API now sets **HTTP 502** (or 503) and returns JSON with `success: false` and a `message`. The frontend should treat any non-2xx or `success === false` as failure and show the error message (and optionally validation errors from the body).

## Related docs and rules

- **Email modal component:** `docs/systems/shared-components/EMAIL_MODAL_COMPONENT.md`
- **Cursor rule (gated forms):** `.cursor/rules/tools-pages-patterns-gated.mdc`
- **Test script:** `v2/scripts/dev-helpers/test-collect-lead.php`
