Hubspot Form Submission Trigger Missing - Can't use webhook

I am looking to move from Relay to n8n. I set up my Hubspot OAuth AND API Service Key in Relay and the OAuth connection was able to set the first step as a Hubspot form submission trigger. I see that’s not possible in n8n yet it has access to the exact same infrastructure.

Feature Request: Can the Form Submission trigger event from Hubspot be added as a Hubspot trigger since it’s clearly been made possible in Relay?

Community ask: I don’t have the enterprise account in Hubspot needed for an alternative webhook step to trigger based off of form submissions. I also can’t trigger off of contact created bc existing customers sometimes complete these Hubspot forms. n8n AI Assistant is pointing me to set up a separate Hubspot Developer API credential. When generating that in Hubspot, I get the API token but n8n wants the app ID, client ID, etc. which the Developer API doesn’t provide. I can get those added client ID and app ID properties if I create a Hubspot app in the public marketplace but that doesn’t seem right. I don’t want this to be public in any way.

Any advice or help here?

For form submissions specifically, the cleanest path without an enterprise account is HubSpot’s free-tier Workflows automation tool: build a simple workflow triggered by “Form submitted,” then add a Webhook action pointing at your n8n Webhook node URL. This works on Starter/Pro tiers, not just Enterprise, and avoids the whole Developer API app registration mess you’re running into with client ID/app ID. You don’t need a public marketplace app for this, HubSpot’s internal workflow webhooks don’t require that. This sidesteps needing the enterprise webhook subscription feature entirely.

Unfortunately, Hubspot has changed this after releasing Data Hub and you can’t do a webhook action in workflows with Starter or Pro tiers of Sales Hub or Marketing Hub. Maybe I’m missing something but here is where it’s explicitly stated in Hubspot. and again at the top of this page. I’d love it if I were missing something here though :slight_smile:

"Send a webhook (Data Hub Professional and Enterprise only)

Trigger a webhook to an external application. This allows your workflow to communicate with this external application. For example, webhooks can send a HubSpot company’s information (formatted in JSON) to an external CRM. This action can be used with all workflow types.

Learn more about triggering webhooks."
1 Like

Good catch, that action does now require Data Hub Pro. Since the workflow webhook action is out, switch to polling instead of pushing: create a HubSpot Private App with the forms scope, then use an n8n Schedule Trigger plus an HTTP Request node calling GET https://api.hubapi.com/marketing/v3/forms/{formId}/submissions with the Private App token as Bearer auth. Store the last processed submittedAt or id (a Data Table or even a simple Set/IF against the previous run) so you only forward new submissions to your CRM step. This avoids both the Data Hub webhook requirement and the public app registration you were trying to avoid. Test the HTTP Request node alone first and confirm the response includes a results array with submittedAt timestamps you can filter on.

Thank you, that’s a good idea. I could set it to run every 15 minutes even and with it only set to grab net new entries - that could definitely work. I’ll try this. Thanks!

Since you’re going to try the 15-minute polling route, I’d make it a small checkpointed workflow rather than a direct “poll → CRM” flow. That way a retry or partial failure does not duplicate form submissions.

A workable n8n shape:

  1. Schedule Trigger — every 15 minutes.
  2. HTTP Request: list HubSpot form submissions — use a HubSpot Private App token as Authorization: Bearer ...; call the submissions endpoint for the specific form you care about.
  3. Code: filter only new submissions — compare submittedAt / submission id against a saved checkpoint.
  4. Split In Batches — process each new submission one at a time.
  5. CRM / downstream steps — create/update whatever you were doing in Relay.
  6. Code: save checkpoint only after success — update the last processed timestamp/id at the end, not before the CRM step.

For the filter/checkpoint Code node, this is the pattern I’d use:

const staticData = $getWorkflowStaticData('global');
const lastSubmittedAt = staticData.lastSubmittedAt || '1970-01-01T00:00:00.000Z';

const submissions = $json.results || [];
const fresh = submissions
  .filter((submission) => new Date(submission.submittedAt) > new Date(lastSubmittedAt))
  .sort((a, b) => new Date(a.submittedAt) - new Date(b.submittedAt));

return fresh.map((submission) => ({
  json: {
    hubspotSubmissionId: submission.id || submission.submissionId,
    submittedAt: submission.submittedAt,
    formId: submission.formId,
    fields: submission.values || submission.submittedValues || [],
    raw: submission,
  },
}));

Then after the downstream CRM step succeeds, add a final Code node:

const staticData = $getWorkflowStaticData('global');
const newest = $input.all()
  .map((item) => item.json.submittedAt)
  .filter(Boolean)
  .sort()
  .at(-1);

if (newest) staticData.lastSubmittedAt = newest;
return $input.all();

One gotcha: workflow static data is saved reliably on active trigger runs, so test the HTTP node manually, but test the checkpoint behavior with the workflow activated. If you need to reprocess a failed window, temporarily clear lastSubmittedAt or move it back a few minutes.

Transparent note: I built this checkpointed polling blueprint with FlowForge AI and adapted it to your HubSpot/Data Hub constraint. The builder is here if you want to generate a fuller n8n JSON version: FlowForge AI — Build Smarter Workflows with AI

1 Like

If HubSpot workflow webhooks are gated for your account, I would avoid the public app path for this. A private app token is enough for an internal polling workflow.

The fallback pattern is:

  1. Create a HubSpot Private App with the minimum CRM and forms scopes you need
  2. In n8n, use a Schedule Trigger instead of a HubSpot trigger
  3. Use HTTP Request with the private app bearer token
  4. Query recent form submissions or recently modified contacts
  5. Filter for the specific form IDs you care about
  6. Store a cursor so each submission is processed once
  7. Keep a dedupe table keyed by form_submission_id if available, or by form_id plus submitted_at plus email/contact id

The important part is separating contact created from form submitted. You are right that contact created will miss existing contacts submitting a new form. Polling form submissions, then deduping by submission identity, is the safer substitute until n8n has a native HubSpot form submission trigger.

I would also design the first version to run every 2 to 5 minutes instead of trying to simulate a true instant trigger. For most form workflows that is close enough, and it avoids depending on a HubSpot webhook action that may disappear with a plan change.

Before building it, I would confirm:

  1. Which exact HubSpot forms should trigger workflows
  2. Whether each form maps to the same downstream action or different branches
  3. What field proves a submission is new
  4. Where processed submission IDs should be stored in n8n

That gives you a private internal workflow without creating a public marketplace app.