# Send leads to HubSpot > Connect a private app's service key, map the fields, and every new lead becomes a contact. Source: https://welcomeai.dev/docs/hubspot Category: Destinations Updated: 2026-09-11 --- ## What you need A private app's **service key** -- the token HubSpot issues on the app's own Auth tab. HubSpot → Settings → Integrations → Private apps → create an app → **Auth** → copy it. It starts with `pat-`. It must be granted these eight scopes: | Scope | Why | | --- | --- | | `crm.objects.contacts.write` | Create or update the contact. Without it nothing can be sent. | | `crm.objects.contacts.read` | Find the contact first, so one person stays one record. | | `crm.schemas.contacts.read` | List the contact properties you map onto. | | `crm.schemas.contacts.write` | Create a new contact property from the route builder. | | `crm.objects.companies.read` / `.write` | HubSpot associates a contact with a company by itself; without these a contact write can fail on the association. | | `crm.schemas.companies.read` / `.write` | List and create company properties. | Routes that land on a Deal need `crm.objects.deals.read` **and** `crm.objects.deals.write`; a route that lands on a Ticket needs `tickets`. The read is not optional: the route builder lists the object's properties before you can map onto them, and a token with only the write fails on that first list with *"HubSpot accepted the token but the private app is missing the deals-read scope"*. **A Personal Access Key is not this.** A HubSpot developer account issues a key carrying `developer.*` and `cms.*` scopes and, at most, CRM *read*; it cannot be granted `crm.objects.contacts.write`, so no lead could ever be written with it however new it is. The credential has to come from a private app inside the CRM portal you want the leads in. ## Connect and route 1. In **Destinations**, connect HubSpot with the token. 2. Create a route: pick the object (usually Contact) and which widget's leads it takes. Leave the widget empty and it takes every lead. 3. Map the fields. **Auto-map** fills in what it is confident about; check it and fix the rest. ## One route per widget, and so one per form A route's scope is a widget. Leave it on all widgets and the route takes every lead; pin it to one and it takes only that widget's. That is also how two forms reach two places, because a widget runs one form: its lead capture names the form it asks, so choosing the widget chooses the form. A contact form on one widget and a demo request on another are two routes, each to its own destination. Two forms on the same widget is not a thing you can set up -- swap the widget's form and you have changed which form it asks. Choosing a widget does one more thing: the mapper's left column shows the questions **that** form asks, instead of every question every form in the workspace asks. So a route built for the demo request is not mapped against the contact form's fields, and the questions on screen are the ones a visitor will actually be asked. Leave the route on all widgets and you get all of them, which is right -- the route takes leads from all of them. One consequence worth knowing: a lead captured by the assistant in conversation carries the widget it came from, so a widget-scoped route takes it like any other. A lead pushed through the API with no widget is taken only by a route with no widget. ## How a lead arrives Contacts are matched by email, so the same person filling in a form twice updates one record rather than creating two. A field with nothing behind it is left out of the request rather than sent empty, because an empty value would overwrite what HubSpot already knows about that person. ## Check it before a visitor does - **Check connection** on the account re-asks HubSpot whether the stored token still works and records the answer with today's date. A token that has been revoked, rotated or whose app was uninstalled comes back from HubSpot as "expired 20703 day(s) ago" -- that number is the age of the unix epoch, not a fact about your token, so we translate it. - **Send sample lead** creates one invented contact through this route, so you can see exactly what arrives. It uses the same mapping a real lead uses. It is not stored as a lead and does not count against your plan; the dispatch log marks it as a sample. ## When it fails The dispatch log under the routes shows every attempt, what we sent and what HubSpot answered. Common answers: - **401 or 403.** The token was revoked, or it lacks a scope. The connection is marked with an error. - **A property does not exist.** Create it in HubSpot, or use **New field** in the route dialog. - **A required property is missing.** The lead had nothing for it. Either map another source or make the property optional in HubSpot. Network problems and HubSpot outages are retried five times over about two hours. A rejected request is not retried, because sending the same wrong thing again will not fix it.