Skip to main content

HubSpot integration

Send profiles, applications, emails and documents from Full Fabric to HubSpot, so it always shows each candidate's latest details and stage.

Overview

The HubSpot integration pushes Full Fabric data into your HubSpot portal: profiles become Contacts, journeys become Deals, sent emails become Email activities, and documents become Notes with file attachments. Data flows one way, from Full Fabric to HubSpot. Edits made in HubSpot are never written back to Full Fabric; to send HubSpot data the other way, use a workflow with the Full Fabric API, as described in Sending HubSpot data to Full Fabric.

Each part of the sync has its own switch, so you can start with Contacts only and add Deals, emails or documents later. All syncing happens in the background within seconds of the change in Full Fabric; staff never trigger it by hand.

Full Fabric record

HubSpot object

Switch

Profile

Contact

Sync profiles

Journey (role on a profile, e.g. an application to a programme)

Deal, associated with the Contact

Sync journeys

Email sent from Full Fabric

Email activity on the Contact timeline

Sync emails

Document uploaded to a profile file field

Note with the file attached, on the Contact

Sync profile attachments

Document in a submitted application

Note with the file attached, on the Deal

Sync application attachments

Setup

An administrator connects one HubSpot portal per institution through OAuth, then turns on the parts of the sync they want. Nothing syncs until a switch is on.

Before you start

  • The HubSpot integration must be enabled for your institution. If HubSpot is missing from the integrations list, contact Full Fabric support.

  • You need the Global integrations permission. New institutions grant it to the Administrator role.

  • In HubSpot you need a user who can install apps in the target portal.

Connect the account

  1. In Full Fabric, open Settings > Integrations > HubSpot.

  2. Click Authenticate. You are sent to HubSpot; log in if asked.

  3. Pick the HubSpot portal to connect and click Choose Account.

  4. Review the access the Full Fabric app requests and approve it. You return to the HubSpot settings page, which now shows the connected portal and the Full Fabric user who connected it.

The app requests read and write access to Contacts and Deals, read access to Deal pipelines, and, optionally, access to Files. Files access is needed only for document sync. If your portal was connected without it, the document switches stay disabled and the page asks you to reconnect.

Full Fabric stores the access token and refreshes it automatically. You never need to reconnect because a token expired.

Turn on the sync

  1. Turn on Sync profiles and review the Contact field mapping (see Data mapping).

  2. Turn on Sync journeys, and set up at least one pipeline rule: its HubSpot pipeline, the programmes and courses it covers, its state to stage table and its Deal field mapping (see Pipeline rules).

  3. Optionally turn on Sync emails.

  4. Optionally turn on Sync profile attachments (needs Sync profiles) and Sync application attachments (needs Sync journeys).

Each switch saves as soon as you change it.

Disconnect

Click Deactivate and confirm. Disconnecting turns off every switch, deletes the Contact mapping and every scoped pipeline rule, resets the catch-all rule, and removes the stored links between Full Fabric records and HubSpot Contacts, Deals and emails. Nothing is deleted in HubSpot. If you reconnect later, you must set up the mappings again, and Full Fabric finds existing Contacts again by email address.

How the sync works

The sync is event-driven: a change in Full Fabric publishes an event, and a background job sends the result to HubSpot, usually within seconds. There is no scheduled batch and no initial import, so records that already exist when you turn a switch on reach HubSpot only the next time they change.

What triggers a sync

Event in Full Fabric

Condition

Result in HubSpot

Profile created

Sync profiles on

Contact found or created, then updated with all mapped fields

Profile updated

Sync profiles on, and a changed field is used in the Contact mapping

Contact updated

Policy accepted (e.g. communication or privacy policy)

Sync profiles on, and that policy is used in the Contact mapping

Contact updated with the new consent values

Journey added or moved to a new state

Sync journeys on, the journey's pipeline rule is on, and the new state is in that rule's stage table

Deal found or created in the rule's pipeline, stage set, all mapped fields updated

Journey or profile field updated

Sync journeys on, the journey's rule is on, the journey already has a Deal, and a changed field is in that rule's Deal mapping

Deal fields updated; the stage is not changed

Email sent from Full Fabric

Sync emails on, and at least one To recipient matches a Contact

One Email activity, associated with every matching Contact

File uploaded to a profile

Sync profiles and Sync profile attachments on, and the profile has a Contact

One Note per new file, associated with the Contact

Application submitted

Sync journeys and Sync application attachments on, and the journey's rule is on

One Note per file in the application, associated with the journey Deals

How records are matched

Contacts. Full Fabric remembers the HubSpot Contact ID on each profile. On the first sync it searches HubSpot for a Contact with the profile's email address and links to it if one exists; otherwise it creates one. A profile is never linked to two Contacts. Changing a profile's email later updates the same Contact.

Deals. Each journey (one profile in one programme intake or course) gets its own Deal, associated with the profile's Contact. Full Fabric remembers the Deal ID on the journey. On the first sync it looks for an existing Deal with the same name on that Contact that no other journey already holds; otherwise it creates one. A journey only gets a Deal when its pipeline rule is on and it reaches a state that rule maps to a stage. Until then, field changes on that journey are not sent.

Emails. Each email is sent to HubSpot once. Only the To recipients are matched to Contacts, and an email whose recipients have no Contact is skipped, not created as a new Contact. CC recipients are shown in the email headers; BCC recipients are never sent.

Documents. Each file is uploaded once to the /fullfabric folder of the HubSpot File Manager as a private file, and a Note with the file attached is created on the record. Files that failed or have not yet passed the virus scan are skipped. Application documents are sent when the application is submitted, because they cannot change after that. If the Deal does not exist yet, Full Fabric retries up to 5 times over about 7.5 minutes, then attaches the files to whichever Deals exist.

How updates are written

  • Two steps. A new Contact or Deal is first created with the minimum required properties (email and name for a Contact; name and stage for a Deal), then updated with the full mapping. If a mapped value is rejected, the record still exists and the next change retries the update.

  • Empty values clear HubSpot. When a mapped Contact field is empty in Full Fabric, the HubSpot property is cleared.

  • First-touch UTM fields are write-once. The first UTM source, medium, campaign, content and term are never overwritten once HubSpot holds a value, and are never cleared.

  • Full Fabric wins. Each sync sends the current Full Fabric value for every mapped property, so a mapped property edited by hand in HubSpot is overwritten on the next change in Full Fabric. Properties you do not map are never touched.

Data mapping

You decide which Full Fabric fields fill which HubSpot properties. There are three mappings: profile fields to Contact properties, journey fields to Deal properties, and journey states to Deal stages. HubSpot properties are read live from your portal, so create a custom property in HubSpot first, then map it. Read-only HubSpot properties are flagged and cannot be written.

Contact mapping (profiles)

Email, First name and Last name are always mapped to email, firstname and lastname and cannot be removed. You can add:

  • Any profile field of a supported type (see Field types below).

  • Built-in profile values: profile ID, profile URL, full name, sortable name, programme name and external ID.

  • Consent: acceptance of the communication policy, plus the channels and topics the person opted into; acceptance of the privacy, cookies and data processing policies.

  • First-touch attribution: first UTM source, medium, campaign, content and term (write-once).

  • Journey fields, prefixed journey..

Deal mapping (journeys)

Each pipeline rule (see below) has its own Deal field mapping. In every rule the Deal name is always mapped and defaults to {{product.long_name}} - {{profile.first_name}} {{profile.last_name}}, for example "MBA 2027 - Ana Costa". You can change the template but not remove it. Keep it specific to one person and one intake: Full Fabric uses the Deal name to find an existing Deal on the first sync. You can add:

  • Any journey field of a supported type.

  • Built-in journey values: state, substate, state and substate, potential.

  • Programme, class and course values: names, programme with class name, course with subject and code, and external IDs.

  • Fields from the class or course Data tab, prefixed class_of. or course..

  • Any Contact-mappable profile field, prefixed profile..

Pipeline rules

A pipeline rule decides whether a journey syncs, which HubSpot Deal pipeline it lands in, at which stage, and with which Deal fields. Create one rule per pipeline. Each rule has:

  • A name and an on/off switch.

  • A HubSpot Deal pipeline (required).

  • A scope: programmes and individual courses. A programme covers all its classes, including intakes added later. A rule with no scope is the catch-all.

  • A stage table: each Full Fabric state and substate you want tracked, mapped to a stage of that rule's pipeline. Stages from other pipelines are rejected.

  • A Deal field mapping, as described above. The pipeline and stage are set by the rule, so they cannot be mapped as fields.

Which rule applies to a journey

  1. A class journey uses the rule that lists the class's programme.

  2. A course journey uses the rule that lists that course.

  3. Any other journey uses the catch-all rule.

  4. If no rule matches, or the matching rule is off, the journey does not sync. A scoped rule that is off does not fall back to the catch-all.

A programme or course can belong to only one rule. The catch-all rule cannot be deleted; switch it off to sync only the programmes and courses you list.

Stages and pipelines. A journey gets a Deal when it enters a state in its rule's stage table. Each later move into a mapped state sets the Deal's stage and pipeline together; moves into unmapped states change nothing. If you move a programme to another rule, its existing Deals move to the new pipeline at their next state change.

Existing setups. Institutions that synced journeys before pipeline rules keep their stage table and Deal mapping as the catch-all rule, shown as Default. Nothing changes until you edit it or add rules. Until you choose the Default rule's pipeline, its Deals keep going to HubSpot's default pipeline.

Composite values

Any mapping can combine several fields and fixed text in one property with {{field}} placeholders, for example {{programme.name}} ({{class_of.name}}). Empty fields become empty text.

Field types

Full Fabric field type

Sent to HubSpot as

Single line text, paragraph, email, phone, country, sequential ID

Text

Number

Number

Date

Midnight UTC of that date, in milliseconds

Date and time

Timestamp in milliseconds

Dropdown, checkbox

Selected options joined with ;, the format HubSpot multi-select properties use

Address

Each part mapped on its own: line 1 to 3, city, state, post code, country

For dropdowns and checkboxes backed by an option set, you can give each option a HubSpot value in the option set settings, so Full Fabric sends HubSpot's internal value instead of the label. Options without a HubSpot value are sent as their label.

When a field is deleted

If a mapped field is removed from a Full Fabric form, its mapping rows are removed automatically. A mapping that still points to a missing field sends an empty value instead of failing the sync.

Errors and troubleshooting

Every failed sync is recorded in the Sync Management report, with the record it concerns and the full HubSpot error. A failed sync is not retried on a timer: it runs again the next time the record changes in Full Fabric, so fix the cause, then edit or re-save the record.

Sync Management report

The report lists failures for HubSpot and other integrations. You can search by profile name, email or ID and filter by integration and sync type. Each entry shows:

  • The record: profile, journey (shown as name and programme), email (shown by subject) or application.

  • The operation: create or update of a Contact or Deal, email sync, profile or application attachment sync.

  • The error message. When HubSpot rejects a property value, the message names the Full Fabric field it came from, for example Profile field "Date of birth" (HubSpot property "date_of_birth").

  • HubSpot details: status, category, correlation ID and the HubSpot record IDs involved. Give the correlation ID to HubSpot support if you contact them.

Viewing the report needs the Sync management permission. Marking entries as handled needs the Sync management changes permission. Both are in the Integrations section of a role's permissions, and an administrator grants them through roles.

Common problems

Symptom

Likely cause

Fix

Existing applicants are not in HubSpot

There is no initial import; only changes after the switch is on are synced

Edit a mapped field on the record to trigger a sync

A journey has no Deal

Its programme or course has no rule and the catch-all is off, its rule is off, its current state is not in the rule's stage table, or Sync journeys was off when it last changed state

Fix the rule, then move the journey or wait for its next state change

Deal fields stop updating

The Deal stage only changes on state changes; field updates need a Deal to exist

Check the journey has a Deal and the changed field is in the Deal mapping

Error naming a property value, e.g. INVALID_OPTION or INVALID_DATE

The Full Fabric value does not fit the HubSpot property type or its options

Align the property type, or set HubSpot values on the option set

Email not on the timeline

No To recipient has a HubSpot Contact, or the email was sent before Sync emails was on

Sync the recipient's profile first; earlier emails are not sent

Document switches are greyed out

The portal was connected without Files access

Reconnect HubSpot and approve Files access

A profile document is missing

The profile had no Contact yet, or the file failed the virus scan

Sync the profile; the next upload sends every file not yet sent

Edits in HubSpot are overwritten

The property is mapped, and Full Fabric is the source of truth

Unmap the property, or edit the value in Full Fabric

Known limits

  • One-way only: nothing flows from HubSpot to Full Fabric.

  • No backfill of existing records, emails or documents.

  • One HubSpot portal per institution.

  • A programme or course belongs to at most one pipeline rule, and every class follows its programme's rule. An existing Deal moves to a new pipeline only at its journey's next state change.

  • Deleting a profile or journey in Full Fabric does not delete the Contact or Deal in HubSpot.

  • Removing a file in Full Fabric does not remove it from HubSpot.

  • If a document upload succeeds but the Note fails, a retry uploads the file again and the first copy stays in the File Manager.

Sending HubSpot data to Full Fabric

The integration does not write to Full Fabric, but you can build the HubSpot to Full Fabric direction yourself with a HubSpot workflow that runs a Custom code action. The action calls the Full Fabric API to find or create a profile, update its fields and add it to a class or course. This works for any HubSpot event a workflow can enrol on: a form submission, a lifecycle stage change, a property update.

What you need

  • HubSpot: Data Hub (formerly Operations Hub) Professional or Enterprise. Custom code actions are not available on other tiers. (HubSpot docs)

  • Full Fabric: an API token from Settings > API Tokens. Create one token just for HubSpot, so you can revoke it on its own. The token is shown only once.

  • The internal names of the Full Fabric fields you want to fill. Call GET /apis/profiles/schema; custom fields start with __, for example __nationality.

  • For a class or course, its ID (GET /apis/institutions/classes) and the state::substate the journey should start in, for example prospect::cold.

How it runs

  1. A Contact enrols in the workflow.

  2. The custom code action receives the Contact properties you selected as inputs.

  3. It looks up the profile by email: GET /apis/profiles/profiles/{email}. OR using the mapped Full Fabric profile id.

  4. It updates the fields with PATCH /apis/profiles/profiles/{id}. Only the fields you send change.

  5. Optionally, it adds the profile to a class or course with POST /apis/profiles/profiles/{id}/roles. A 405 means the profile already has a journey there, which is fine.

  6. It returns the Full Fabric profile ID as an output, which a later Edit record action can copy into a Contact property.

If the HubSpot integration is on, the new profile then syncs back to HubSpot and links to the same Contact by email, so no duplicate is created.

Set up the action

  1. In HubSpot, create a Contact-based workflow and set its enrolment trigger.

  2. Add the action Data ops > Custom code, language Node.js.

  3. Under Secrets, add FF_API_TOKEN (the token) and FF_SUBDOMAIN (your institution's subdomain, the part before .fullfabric.cloud).

  4. Under Property to include in code, add email, firstname, lastname and every property you want to send.

  5. Paste the code below and change the fields block to your mapping.

  6. Under Data outputs, add ff_profile_id as a string.

  7. Use Test action on one Contact, check the profile in Full Fabric, then turn the workflow on.

Example code

const axios = require('axios');

const ff = axios.create({
baseURL: `https://${process.env.FF_SUBDOMAIN}.fullfabric.cloud/apis/profiles`,
headers: {
'FF-Auth': process.env.FF_API_TOKEN,
'X-API-Version': '2026-06-26',
'Content-Type': 'application/json'
},
timeout: 15000
});

// Optional: add new profiles to a class or course.
const CONTEXT_ID = null; // e.g. '6385bb46976a771912003e6a'
const INITIAL_STATE = 'prospect::cold';

async function findProfile(email) {
try {
const { data } = await ff.get(`/profiles/${encodeURIComponent(email)}`);
return data;
} catch (error) {
if (error.response && error.response.status === 404) return null;
throw error;
}
}

exports.main = async (event, callback) => {
const input = event.inputFields;
const email = (input.email || '').trim().toLowerCase();
if (!email) throw new Error('Contact has no email');

// HubSpot property -> Full Fabric field. Empty values are skipped,
// so a blank HubSpot property never clears data in Full Fabric.
const fields = Object.fromEntries(Object.entries({
__nationality: input.nationality,
__gmat_score: input.gmat_score
}).filter(([, value]) => value !== undefined && value !== null && value !== ''));

let profile = await findProfile(email);

if (profile) {
if (Object.keys(fields).length) {
await ff.patch(`/profiles/${profile.id}`, { profile: fields });
}
} else {
const { data } = await ff.post('/profiles', {
profile: { email, first_name: input.firstname, last_name: input.lastname, ...fields }
}, {
headers: { 'Idempotency-Key': `hubspot-contact-${event.object.objectId}-create` }
});
profile = data;

if (CONTEXT_ID) {
try {
await ff.post(`/profiles/${profile.id}/roles`, {
context_id: CONTEXT_ID,
state_and_substate: INITIAL_STATE
});
} catch (error) {
if (!error.response || error.response.status !== 405) throw error;
}
}
}

callback({ outputFields: { ff_profile_id: profile.id } });
};

Things to get right

  • Body shape. Profile create and update take the fields inside a profile object. Field names are the internal names from the schema, not the labels.

  • Rejected values. Create and update answer 406 with the failing field, for example a dropdown value that is not one of its options. Dropdowns and checkboxes expect the option value Full Fabric stores, so translate HubSpot values in the code where they differ.

  • Required fields. Create needs first_name, last_name and email. If the email already belongs to a profile, create fails, which is why the code looks up first.

  • Consent. If your institution requires data processing consent, send it under profile.policies (data_processing_consent_accept, data_processing_consent_date, data_processing_consent_source), and only when HubSpot actually holds that consent.

  • Avoid loops. If the HubSpot integration also writes a property back to the Contact, do not use that property in the enrolment trigger with re-enrolment on. Otherwise each Full Fabric update re-enrols the Contact and calls Full Fabric again.

  • Limits. A custom code action must finish within 20 seconds and 128 MB. Throwing an error makes HubSpot retry rate-limit and server errors for up to three days, and the Idempotency-Key stops a retried create from running twice. Other errors show on the workflow's action history.

  • Data protection. The token can read and change profiles. Keep it in HubSpot secrets only, never in the code, and send only the fields you need.

FAQ

Does a change in HubSpot update Full Fabric? Not through the integration, which only writes to HubSpot. A HubSpot workflow with a custom code action can call the Full Fabric API to do it; see Sending HubSpot data to Full Fabric.

Will it create duplicate Contacts? No, as long as the email address matches. Full Fabric looks up the Contact by email before creating one and then remembers its ID. A person whose HubSpot Contact uses a different email gets a second Contact.

One applicant applies to two programmes. What do we see? One Contact with two Deals, one per journey, each in the pipeline of the rule that covers its programme. An application to several programmes at once creates one Deal per primary choice, and its documents attach to all of them.

How fast is it? Usually a few seconds after the change. Application documents can take up to about 8 minutes when the Deal is still being created.

Can we choose which emails sync? No. With Sync emails on, every email sent from Full Fabric to a known Contact is logged once. Incoming emails are not synced.

What consent data reaches HubSpot? Only what you map: whether the person accepted the communication, privacy, cookies and data processing policies, and the communication channels and topics they chose. A new acceptance syncs straight away.

Does disconnecting delete our HubSpot data? No. It removes the connection, the mappings and Full Fabric's stored HubSpot IDs only.

Who can change the settings? Staff with the Global integrations permission.

Can we use HubSpot forms to create Full Fabric profiles? That is a separate feature; see How to Integrate HubSpot Forms with Full Fabric.

Did this answer your question?