-
-
Save jackcoldrick90/faa4f25eb6f07a6bc50b84589a574b3d to your computer and use it in GitHub Desktop.
// Import the Hubspot NodeJS Client Library - this will allow us to use the HubSpot APIs | |
const hubspot = require('@hubspot/api-client'); | |
/* | |
This function is called when the custom code action is executed. It takes 2 arguements. The first is the event object which contains information on the currently enrolled object. | |
The second is the callback function which is used to pass data back to the workflow. | |
*/ | |
exports.main = (event, callback) => { | |
// Instantiate a new HubSpot API client using the HAPI key (secret) | |
const hubspotClient = new hubspot.Client({ | |
accessToken: process.env.HUBSPOTTOKEN | |
}); | |
// Retrive the currently enrolled contacts "company" property | |
hubspotClient.crm.contacts.basicApi.getById(event.object.objectId, ["company"]) | |
.then(results => { | |
// Get data from the results and store in variables | |
let companyName = results.body.properties.company; | |
//console.log("SEARCH TERM: " + companyName); // - FOR DEBUG | |
// Create search criteria | |
const filter = { propertyName: 'name', operator: 'EQ', value: companyName } | |
const filterGroup = { filters: [filter] } | |
const sort = JSON.stringify({ propertyName: 'name', direction: 'DESCENDING'}) | |
const properties = ['name'] | |
const limit = 1 | |
const after = 0 | |
const searchCriteria = { | |
filterGroups: [filterGroup], | |
sorts: [sort], | |
properties, | |
limit, | |
after | |
} | |
// Search the CRM for Companies matching "companyName" variable defined earlier | |
hubspotClient.crm.companies.searchApi.doSearch(searchCriteria).then(searchCompanyResponse => { | |
//console.log("RESULTS: " + searchCompanyResponse.body.total); // - FOR DEBUG | |
// If total equals 0 no results found | |
if(searchCompanyResponse.body.total == 0){ //NO MATCH FOUND - CREATE COMPANY AND ASSOCIATE | |
// console.log("COMPANY " + companyName + "NOT FOUND: CREATE + ASSOCIATE") // - FOR DEBUG | |
//Create a Company object | |
const companyObj = { | |
properties: { | |
name: companyName, | |
}, | |
} | |
//Create the Company using Company object above | |
hubspotClient.crm.companies.basicApi.create(companyObj).then(companyCreateResponse =>{ | |
//Associate Company with Contact using the ID returned from the previous request | |
hubspotClient.crm.companies.associationsApi.create(companyCreateResponse.body.id,'contacts', event.object.objectId,'company_to_contact'); | |
}); | |
}else{ // MATCH FOUND - ASSOCIATE COMPANY TO CONTACT | |
// console.log("COMPANY " + companyName + " FOUND: ASSOCIATE RECORDS"); // - FOR DEBUG | |
//Associate Company with Contact | |
hubspotClient.crm.companies.associationsApi.create(searchCompanyResponse.body.results[0].id,'contacts', event.object.objectId,'company_to_contact'); | |
} | |
}); | |
callback({outputFields: {}}); | |
}) | |
.catch(err => { | |
console.error(err); | |
}); | |
} |
Hey @jackcoldrick90 - I've just copy/pasted this over to a sandbox portal, changed the secret and hit test against a contact record (within the action editor), the logs returned the following:
TypeError: Cannot read properties of undefined (reading 'properties')
I ran it by Phind AI, which suggested results.body was undefined, so I added the following to debug a little:
hubspotClient.crm.contacts.basicApi.getById(event.object.objectId, ["company"])
.then(results => {
console.log(results);
if (results.body) {
// rest of code...
} else {
console.log("results.body is undefined");
}
})
.catch(err => {
console.error(err);
});
}
The logs then returned:
2023-11-01T11:32:37.344Z INFO SimplePublicObjectWithAssociations {
id: '201',
properties: {
company: 'Dunder Mifflin',
createdate: '2019-12-06T15:25:27.197Z',
hs_object_id: '201',
lastmodifieddate: '2023-11-01T10:08:32.513Z'
},
createdAt: 2019-12-06T15:25:27.197Z,
updatedAt: 2023-11-01T10:08:32.513Z,
archived: false
}
2023-11-01T11:32:37.403Z INFO results.body is undefined
Memory: 73/128 MB
Runtime: 1517.77 ms
I'm not very code-savvy and can just about read it and not much else, so I've hit a bit of a brick wall. Do you have any ideas as to where the code is falling over in my instance?
Following a successful poc that this works, I'll be tweaking the code slightly to use a custom contact/company property to function off of, is there anything worth considering using a custom property instead of a vanilla HS property?
EDIT: I forgot to add, I'd also like to set an association label when the association is made (and add it if it is missing) - but will see where I get to with this first!
I would be extremely in your debt if you or any others could give guidance 🙏, thank you!
Hey @Steve-Kv, it's been a while since this Gist got created. The response objects from the API look a bit different now, that's why you're getting these errors. You should be able to use the v3 of the API library instead of v8. Then it should work.
@jackcoldrick90 I want to edit this so that I can associate a custom object with contacts that match (based on email domain field) but I don't want it to create anything if the search turns up nothing. Would all I have to do is remove the "if total equals zero" and "create company" sections?
Hey @jackcoldrick90 and everyone here. I wanted to ask if you all have been successful in adapting this code for other object associations to use in a custom coded workflow, specifically creating a deal-to-contact association automatically based on a common field/variable value that is always an email address. The goal is to automate the association process for Deals and their existing Contacts. Here has been my logic in adapting this code for my purposes, please let me know if I am missing something here:
-
I do not need to look for the deal itself as it is available through the workflow enrollment; I just define the Deal Record ID and then use it in the code.
const hs_deal_id = event.inputFields['hs_object_id'];
-
My next task is to use an existing field value which is always an email address to look for the existing correlating contact object (these contacts will always be in the system). I am assuming much of the filtering above is not needed for this? Can I adapt the search filters to include only the email as the property like this:
const filter = { propertyName: 'email', operator: 'EQ', value: contactEmail }
const filterGroup = { filters: [filter] }
const sort = JSON.stringify({ propertyName: 'email', direction: 'DESCENDING'})
const properties = ['email']
const limit = 1
const after = 0
const searchCriteria = {
filterGroups: [filterGroup],
sorts: [sort],
properties,
limit,
after
}
And then I would run the rest of the code but instead of company, search through contacts and adjust the association to be deals to contacts? I want to make sure that I am on the right track. Thank you in advance!
Hey @jackcoldrick90, Im getting this code.. Im new to coding and hubspot API.. I tried to copy and paste your code.. I also created the Private app key to allow read and write contacts and companies. Im not sure what to change, I named my private app as HUBSPOTTOKEN.
what should be changed in your code so I can get it working?
2024-10-07T20:47:33.277Z ERROR ApiException [Error]: HTTP-Code: 401
Message: An error occurred.
Body: {"status":"error","message":"Authentication credentials not found. This API supports OAuth 2.0 authentication and you can find more details at https://developers.hubspot.com/docs/methods/auth/oauth-overview","correlationId":"8a4f31ee-e1d2-447e-9969-177fb1983ad3","category":"INVALID_AUTHENTICATION"}
Headers: {"access-control-allow-credentials":"false","cf-cache-status":"DYNAMIC","cf-ray":"8cf0bf387e6c8262-IAD","connection":"keep-alive","content-length":"299","content-type":"application/json;charset=utf-8","date":"Mon, 07 Oct 2024 20:47:33 GMT","nel":"{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}","report-to":"{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=612KJLOxSRgjV%2BOs705p%2B07FOqiTMyS5bdVIx6p%2FZSwTvShSMajOHx3pQDUQ%2FCbgoa%2FrYObIN9g9CzzQ6oF1Jfp6ItNvlUZDAR%2BIT4w171LXziRWArtVK0y1RaR65UtD"}],"group":"cf-nel","max_age":604800}","server":"cloudflare","strict-transport-security":"max-age=31536000; includeSubDomains; preload","vary":"origin, Accept-Encoding","x-content-type-options":"nosniff","x-hubspot-auth-failure":"401 Unauthorized","x-hubspot-correlation-id":"8a4f31ee-e1d2-447e-9969-177fb1983ad3"}
at BasicApiResponseProcessor. (/opt/nodejs/node_modules/@hubspot/api-client/lib/codegen/crm/contacts/apis/BasicApi.js:227:23)
at Generator.next ()
at fulfilled (/opt/nodejs/node_modules/@hubspot/api-client/lib/codegen/crm/contacts/apis/BasicApi.js:5:58)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
code: 401,
body: {
status: 'error',
message: 'Authentication credentials not found. This API supports OAuth 2.0 authentication and you can find more details at https://developers.hubspot.com/docs/methods/auth/oauth-overview',
correlationId: '8a4f31ee-e1d2-447e-9969-177fb1983ad3',
category: 'INVALID_AUTHENTICATION'
},
headers: {
'access-control-allow-credentials': 'false',
'cf-cache-status': 'DYNAMIC',
'cf-ray': '8cf0bf387e6c8262-IAD',
connection: 'keep-alive',
'content-length': '299',
'content-type': 'application/json;charset=utf-8',
date: 'Mon, 07 Oct 2024 20:47:33 GMT',
nel: '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}',
'report-to': '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=612KJLOxSRgjV%2BOs705p%2B07FOqiTMyS5bdVIx6p%2FZSwTvShSMajOHx3pQDUQ%2FCbgoa%2FrYObIN9g9CzzQ6oF1Jfp6ItNvlUZDAR%2BIT4w171LXziRWArtVK0y1RaR65UtD"}],"group":"cf-nel","max_age":604800}',
server: 'cloudflare',
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
vary: 'origin, Accept-Encoding',
'x-content-type-options': 'nosniff',
'x-hubspot-auth-failure': '401 Unauthorized',
'x-hubspot-correlation-id': '8a4f31ee-e1d2-447e-9969-177fb1983ad3'
}
}
@MariaFernanda1997 - The code you need would look something like this: https://gist.github.com/jackcoldrick90/24e96f8e6d962e7c7ecd787f8df6b052.
It first retrieves the deal linked to the custom object. It then uses the CRM Associations API to retrieve the contact linked to the deal. It then uses the CRM Associations API to link the custom object to the contact. If your deal has more than one contact you will need to make additional requests and modify.
I hope it helps.