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
In Full Fabric, open Settings > Integrations > HubSpot.
Click Authenticate. You are sent to HubSpot; log in if asked.
Pick the HubSpot portal to connect and click Choose Account.
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
Turn on Sync profiles and review the Contact field mapping (see Data mapping).
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).
Optionally turn on Sync emails.
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.orcourse..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
A class journey uses the rule that lists the class's programme.
A course journey uses the rule that lists that course.
Any other journey uses the catch-all rule.
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 |
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. | 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 thestate::substatethe journey should start in, for exampleprospect::cold.
How it runs
A Contact enrols in the workflow.
The custom code action receives the Contact properties you selected as inputs.
It looks up the profile by email:
GET /apis/profiles/profiles/{email}. OR using the mapped Full Fabric profile id.It updates the fields with
PATCH /apis/profiles/profiles/{id}. Only the fields you send change.Optionally, it adds the profile to a class or course with
POST /apis/profiles/profiles/{id}/roles. A405means the profile already has a journey there, which is fine.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
In HubSpot, create a Contact-based workflow and set its enrolment trigger.
Add the action Data ops > Custom code, language Node.js.
Under Secrets, add
FF_API_TOKEN(the token) andFF_SUBDOMAIN(your institution's subdomain, the part before.fullfabric.cloud).Under Property to include in code, add
email,firstname,lastnameand every property you want to send.Paste the code below and change the
fieldsblock to your mapping.Under Data outputs, add
ff_profile_idas a string.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
profileobject. Field names are the internal names from the schema, not the labels.Rejected values. Create and update answer
406with 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_nameandemail. 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-Keystops 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.
