HubSpot + Laravel Forms: Sending Leads Where Sales Lives | FilaForms                                 [ ![Filaforms Logo](https://filaforms.app/logo.svg)FilaForms

 ](https://filaforms.app)  [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app#pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app)  [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app#pricing) 

 [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app#pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app) [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app#pricing) 

   ![FilaForms](https://filaforms.app/logo.svg) FilaForms 

 IntegrationsHubSpot + Laravel Forms: Sending Leads Where Sales Lives
========================================================

 filaforms.app/blog

  [    Back to blog ](https://filaforms.app/blog) [ Integrations ](https://filaforms.app/blog/category/integrations) 

HubSpot + Laravel Forms: Sending Leads Where Sales Lives
========================================================

 Manuk Minasyan ·  July 10, 2026  · 8 min read 

 A demo form fills out at 4pm on a Friday. The lead is hot — the prospect just watched the pricing video, clicked through to the trial page, then back to the contact form and asked for a call. Your Sales team lives in HubSpot, deal pipeline in one tab and Gmail in the other.

They will not see this lead until Monday morning, when somebody logs into the Filament admin panel and notices the row. By then a competitor has already replied. The lead went cold not because the product lost — it lost because the form lived in the wrong place.

This is the gap a `hubspot laravel form integration` exists to close. Submission lands in your Laravel app, a queued listener pushes it into HubSpot within seconds, the new contact shows up in the Sales rep's view next to every other prospect they're working. No Zapier hop. No Monday-morning catch-up. And, as the rest of this post explains, one early decision that determines whether your marketing reports tell the truth a month later. We built [the lead-gen form template](/blog/lead-generation-forms-in-laravel-a-template-that-converts) for the form half; this is the wiring half.

Two HubSpot APIs to choose from
-------------------------------

HubSpot exposes two ways to do this job, and they are not interchangeable. The first is the Forms API — you POST a submission to a HubSpot-managed form, HubSpot then creates the contact, deduplicates by email, and fires its form-tracking layer so the submission shows up in HubSpot's analytics with full attribution. The second is the CRM Contacts API — you POST a contact object directly to `/crm/v3/objects/contacts` and HubSpot creates a contact row, full stop. No form, no analytics, no source attribution.

Pick the Forms API for most cases. It costs an extra ten minutes — you have to create a form inside HubSpot first to get the `formGuid` — and in exchange you keep marketing's reports intact. Conversion paths, original source, page tracking, and the Forms dashboard all keep working as if a user had filled out a HubSpot-hosted form, because as far as HubSpot is concerned, they did.

Pick the Contacts API only when you have a reason to bypass HubSpot's form layer: a CRM-first workflow where the lead exists before any form is filled, an enrichment pipeline writing to existing records, or a B2B sales motion where marketing analytics do not matter.

Setting up HubSpot credentials
------------------------------

Before either API accepts your request, you need a Private App token. In HubSpot, open Settings → Integrations → Private Apps → Create a private app. Name it "Laravel Form Sync". On the Scopes tab, pick what you need: `forms` for the Forms API path, `crm.objects.contacts.write` for the Contacts API path. If you might switch later, take both.

Hit Create, then copy the access token from the resulting modal. This is the only time you see it. Drop it into `.env` as `HUBSPOT_TOKEN`, reference it from `config/services.php`, and treat it like a password — scoped, rotatable, never logged. The older API-key auth in stale tutorials is deprecated; Private App tokens replaced it. If a tutorial tells you to put `hapikey=...` in your query string, the tutorial is from 2022 and the endpoint will reject you.

For the Forms API you also need the `portalId` (your HubSpot account ID, visible in any HubSpot URL) and the `formGuid` of the HubSpot form you want to submit against. Create that form in HubSpot first, even if you never display it on a page — its only job is to be a destination.

Wiring it to FilaForms via Forms API
------------------------------------

FilaForms fires a `FormSubmitted` event on every submission. The listener POSTs to [HubSpot's authenticated submissions endpoint](https://developers.hubspot.com/docs/api-reference/legacy/marketing/forms/v3-legacy/submit-data-authenticated) with the Private App token as a Bearer header:

```
namespace App\Listeners;

use FilaForms\Core\Events\FormSubmitted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Http;

class SyncSubmissionToHubSpot implements ShouldQueue
{
    public function handle(FormSubmitted $event): void
    {
        $data = $event->submission->data;

        Http::withToken(config('services.hubspot.token'))
            ->post(sprintf(
                'https://api.hsforms.com/submissions/v3/integration/secure/submit/%s/%s',
                config('services.hubspot.portal_id'),
                config('services.hubspot.form_guid'),
            ), [
                'fields' => [
                    ['objectTypeId' => '0-1', 'name' => 'email',     'value' => $data['email']     ?? ''],
                    ['objectTypeId' => '0-1', 'name' => 'firstname', 'value' => $data['first_name'] ?? ''],
                    ['objectTypeId' => '0-1', 'name' => 'lastname',  'value' => $data['last_name']  ?? ''],
                    ['objectTypeId' => '0-1', 'name' => 'company',   'value' => $data['company']   ?? ''],
                ],
            ])
            ->throw();
    }
}

```

The `objectTypeId` of `0-1` means contact — HubSpot uses internal IDs for object types, and contact is `0-1`. The `name` values are HubSpot's internal property names, not the labels marketing sees in the UI. More on that in a moment.

`ShouldQueue` is non-negotiable. The form response should not block on a HubSpot round-trip, and the queue gives you free retry on transient failures. The listener sits on the same primitive that powers [the queued delivery layer](/blog/webhooks-in-filaforms-send-submissions-anywhere) under every other integration in this series.

Wiring via Contacts API
-----------------------

The Contacts API path is shorter and uglier. POST to [HubSpot's CRM Contacts endpoint](https://developers.hubspot.com/docs/api/crm/contacts) with the same Bearer token, body shape changes:

```
Http::withToken(config('services.hubspot.token'))
    ->post('https://api.hubapi.com/crm/v3/objects/contacts', [
        'properties' => [
            'email'     => $data['email']      ?? null,
            'firstname' => $data['first_name'] ?? null,
            'lastname'  => $data['last_name']  ?? null,
            'company'   => $data['company']    ?? null,
        ],
    ])
    ->throw();

```

Cleaner code, fewer concepts. The contact lands in HubSpot, deduplicates against existing contacts by email, and shows up in the Contacts list. What does not happen: form-tracking analytics, original source attribution, conversion-path data, the Forms dashboard, any of HubSpot's marketing-side reporting that depends on a submission event having fired.

Use this when you know none of that matters — internal tools, partner-portal forms, enrichment jobs — or when you specifically want to bypass HubSpot's form layer for compliance reasons. Otherwise the Forms API is the right default.

Field mapping
-------------

HubSpot's internal property names are not the labels you see in the UI. "First Name" in the Contacts table is `firstname` in the API. Get them wrong and the request fails or, worse, succeeds while writing your data into the wrong column. Here is the everyday mapping for a lead-gen form:

FilaForms field labelHubSpot internal propertyEmail`email`First Name`firstname`Last Name`lastname`Company`company`Phone`phone`Job Title`jobtitle`Website`website`Custom properties — something like "Project Budget" or "Use Case" — have to exist in HubSpot before you can write to them. Create them in Settings → Properties → Contact properties, copy the internal name from the property details, then add it to your mapping. The API will silently drop unknown fields on the Forms API path and reject the whole record on the Contacts API path.

Edge cases
----------

[HubSpot's rate limits for Private Apps](https://developers.hubspot.com/docs/api/usage-details) are documented but easy to miss. Free and Starter accounts get 100 requests per 10 seconds per app. Professional and Enterprise get 190. A burst of submissions — a webinar landing page, a newsletter blast linking to a contact form — can trip this. The queue handles it with backoff. A 429 response retries on its own.

HubSpot dedupes contacts by email automatically on both APIs. Submit the same email twice and HubSpot updates the existing contact instead of creating a duplicate. This is the right behaviour for lead-gen and means you do not need a "check before insert" step in your listener.

Token rotation matters. When somebody leaves the team, regenerate the Private App token and roll it through `.env` and the queue worker config — same lifecycle as a deploy key.

What we got wrong
-----------------

Our first version posted straight to the Contacts API. The endpoint URL was shorter, the response was tidier, and the code passed review in an afternoon. Contacts appeared in HubSpot. Sales saw the leads. Everyone declared victory.

Two weeks later, marketing pulled the weekly attribution report and the column for "Source" on every new contact from our app was empty. No conversion path. No original page. No campaign tag. From HubSpot's reporting layer, these contacts had appeared by magic — the form-tracking event had never fired, because we had not gone through a HubSpot form. Marketing's quarterly content-attribution review was in three days. The fix took an afternoon: create a HubSpot form to be the destination, swap the endpoint to `api.hsforms.com`, ship the listener again. The lesson generalises past HubSpot: an integration that bypasses a downstream tool's tracking layer breaks attribution silently, and silent breakage is the worst kind. Read the reporting side of the destination tool before you pick the API.

Try it
------

If your sales pipeline lives in HubSpot, the Forms API path lands leads in front of the right people inside a minute of submission with attribution intact. If sales lives somewhere lighter, [the Notion-as-CRM workflow](/blog/notion-as-a-crm-for-form-submissions-laravel-workflow) is the same listener pattern aimed at a different destination — useful for teams who haven't outgrown a database-of-rows yet.

If you haven't set up FilaForms yet, you can [set up FilaForms here](https://filaforms.app). The pattern in this post rides on [FilaForms' outgoing webhooks](/blog/webhooks-in-filaforms-send-submissions-anywhere) — one queued listener, one HTTP call, one decision about which API to send it to.

 Related posts
-------------

 [  Integrations   Aug 4, 2026  

 Microsoft Teams Form Notifications for Enterprise Laravel Apps 
----------------------------------------------------------------

Slack doesn't fly at the enterprise. Teams is the stack. The webhook story changed in 2025 — here's the current working path for posting form submissions to a Teams channel from Laravel.

 ](https://filaforms.app/blog/microsoft-teams-form-notifications-laravel) [  Integrations   Jul 24, 2026  

 Mailchimp Integration: Auto-Subscribe Form Respondents 
--------------------------------------------------------

Newsletter form fills. Email lands in Laravel. Mailchimp's audience stays empty until someone exports manually. A queued listener + the Lists API closes the gap — with the double-opt-in note that keeps EU lawyers happy.

 ](https://filaforms.app/blog/mailchimp-integration-auto-subscribe-form-respondents) [  Integrations   Jul 3, 2026  

 Airtable for Form Submissions: Two-Way Sync with Laravel 
----------------------------------------------------------

Most form integrations are one-way. This one runs both directions — submissions to Airtable, status changes back. The full loop in a Laravel app.

 ](https://filaforms.app/blog/airtable-for-form-submissions-two-way-sync-laravel) 

    ![FilaForms Logo](/logo.svg) FilaForms 

 Laravel form infrastructure for Filament. Stop rebuilding forms on every project.

 ### Product

 [ Features ](https://filaforms.app#features) [ Documentation ](https://docs.filaforms.app) [ Blog ](https://filaforms.app/blog) [ Pricing ](https://filaforms.app#pricing) [ About ](https://filaforms.app/about) [ Contact ](mailto:hello@filaforms.app) 

 ### Legal

 [ Terms of Service ](https://filaforms.app/terms-of-service) [ Privacy Policy ](https://filaforms.app/privacy-policy) 

  © 2025-2026 FilaForms. All rights reserved.

 [    ](mailto:hello@filaforms.app) [    ](https://x.com/MinasyanManuk)
