Skip to content

fix(newsletter): move HubSpot submission to main process and enrich payload #2312

@peppescg

Description

@peppescg

Context

The newsletter form in ToolHive Studio submits directly from the renderer to https://api.hsforms.com/submissions/v3/integration/submit/{portalId}/{formId}. Because the form is custom (not embedded), the HubSpot SDK does not allow reCAPTCHA on this endpoint — so we currently have no bot protection, and the marketing team is seeing 4-5 spam submissions per day (manually triaged in HubSpot for now).

Slack discussion: group DM with @ariel and @daniel, 2026-06-03

Goal

First incremental step toward reducing bot spam, without breaking submissions for legitimate users. This issue covers the first two bullets of Step 1 of the plan:

  • Move the HubSpot submission from the renderer to the Electron main process via IPC.
    • The HTTP call to api.hsforms.com happens in main, not in renderer.
    • Form ID and portal ID live in main (or env) and are no longer present in the renderer bundle.
    • The request no longer appears in DevTools network tab.
  • Enrich the submission payload with extra signals to validate against (alongside the existing instance_id):
    • app_version
    • platform (darwin / win32 / linux)
    • client_type (e.g. toolhive-studio-desktop)

Why

  • Removing form/portal IDs from the renderer bundle makes them harder for bots to scrape from a public download.
  • Hiding the call from DevTools removes the easiest path to copy-paste-replay a submission.
  • The extra fields give HubSpot Workflows something concrete to validate against — missing/malformed values can be auto-flagged as spam in a follow-up.

This is not a complete fix on its own: a determined bot can still craft fake submissions. The follow-up bullets of Step 1 (form ID rotation, HubSpot Workflow for spam) and Step 2 below are tracked separately.

Acceptance criteria

  • Newsletter submission no longer appears as a network request from the renderer process.
  • HubSpot form ID / portal ID are not present in the renderer bundle (verify with a packaged build + DevTools / source search).
  • Payload received by HubSpot contains instance_id, app_version, platform, client_type.
  • Existing UX (success/error states, validation) is unchanged from the user's perspective.
  • Unit tests updated to cover the IPC path and the enriched payload shape.

Out of scope (future work)

Rest of Step 1

  • Rotate the HubSpot form ID (create new form, delete old one — bots likely have the current ID hardcoded from a prior network capture).
  • HubSpot Workflow that auto-marks as spam + deletes submissions where instance_id, app_version, platform, or client_type are missing or malformed.

Step 2 — if bots adapt and start sending faked fields

  • Stand up an internal endpoint on the stacklok.com WordPress site that the desktop app POSTs to instead of HubSpot directly.
  • The WordPress endpoint validates a Cloudflare Turnstile token server-to-server and signs the submission with an HMAC field only WordPress can produce.
  • HubSpot Workflow drops anything without a valid HMAC, so bots posting directly to api.hsforms.com get auto-trashed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions