I ran into the same challenge – having to build manual workflows just to get the Record ID onto the ticket. The process of going from listings and deals → to the contact → and then from the contact → to the ticket is just unnecessarily complex.
Honestly, it’s mind-boggling that HubSpot doesn’t support something this basic out of the box. The whole point of associations starts to feel pretty pointless when you can’t easily cross-associate objects.
Anyway, Claude 4.0 came to the rescue with a custom API call through Operations Hub. 
So if anyone else is stuck in the same situation – here’s the code, totally free to use: 
const hubspot = require('@hubspot/api-client');
exports.main = async (event) => {
const hubspotClient = new hubspot.Client({
accessToken: process.env.HAPIKEY,
});
const ticketId = event.inputFields['hs_ticket_id'];
const contactId = event.inputFields['hs_contact_id'];
const LISTINGS_OBJECT_TYPE_ID = '0-420';
try {
console.log(`🎫 Ticket ID: ${ticketId}`);
console.log(`👤 Contact ID: ${contactId}`);
if (!contactId) {
console.log("❗️No contact ID provided.");
return { success: false, error: "No contact ID" };
}
// LØSNING 1: Hent deals
console.log("🔍 Fetching deals using SDK...");
let dealIds = [];
try {
const dealsResponse = await hubspotClient.crm.associations.v4.basicApi.getPage(
'contacts',
contactId,
'deals'
);
dealIds = dealsResponse?.results?.map(r => r.toObjectId) ?? [];
console.log(`💼 Found ${dealIds.length} deals`);
} catch (dealError) {
console.log("⚠️ SDK deals failed, trying fallback...", dealError.message);
try {
const contactDeals = await hubspotClient.crm.contacts.associationsApi.getAll(
contactId,
'deals'
);
dealIds = contactDeals?.results?.map(r => r.id) ?? [];
} catch (basicError) {
console.log("❌ Basic deals API also failed:", basicError.message);
}
}
console.log(`💼 Final deals count: ${dealIds.length}`);
// LØSNING 2: Hent listings med flere strategies
console.log("🔍 Fetching listings with multiple strategies...");
let listingIds = [];
// Strategy 1: Forskellige object identifiers
const listingIdentifiers = [LISTINGS_OBJECT_TYPE_ID, 'listings', '2-420', 'p420'];
for (const identifier of listingIdentifiers) {
if (listingIds.length > 0) break;
console.log(`🔄 Trying identifier: ${identifier}`);
try {
const listingsResponse = await hubspotClient.crm.associations.v4.basicApi.getPage(
'contacts',
contactId,
identifier
);
listingIds = listingsResponse?.results?.map(r => r.toObjectId) ?? [];
if (listingIds.length > 0) {
console.log(`✅ Found ${listingIds.length} listings with identifier ${identifier}`);
break;
}
} catch (error) {
console.log(`❌ Identifier ${identifier} failed:`, error.message);
}
}
// Strategy 2: v3 associations
if (listingIds.length === 0) {
console.log("🔄 Trying v3 associations API...");
try {
const v3Response = await hubspotClient.crm.contacts.associationsApi.getAll(
contactId,
LISTINGS_OBJECT_TYPE_ID
);
listingIds = v3Response?.results?.map(r => r.id) ?? [];
console.log(`📋 v3 found ${listingIds.length} listings`);
} catch (v3Error) {
console.log("❌ v3 associations failed:", v3Error.message);
}
}
// Strategy 3: Direct search
if (listingIds.length === 0) {
console.log("🔄 Searching for listings directly...");
const searchFilters = [
{ propertyName: 'associations.contact', operator: 'EQ', value: contactId },
{ propertyName: 'hs_associated_contact_id', operator: 'EQ', value: contactId },
{ propertyName: 'contact_id', operator: 'EQ', value: contactId }
];
for (const filter of searchFilters) {
if (listingIds.length > 0) break;
try {
const searchResponse = await hubspotClient.crm.objects.searchApi.doSearch(LISTINGS_OBJECT_TYPE_ID, {
filterGroups: [{ filters: [filter] }],
limit: 100,
properties: ['hs_object_id']
});
listingIds = searchResponse?.results?.map(r => r.id) ?? [];
if (listingIds.length > 0) {
console.log(`✅ Found ${listingIds.length} listings with search`);
break;
}
} catch (searchError) {
console.log(`❌ Search failed:`, searchError.message);
}
}
}
console.log(`📋 Final listings count: ${listingIds.length}`);
// Debug hvis ingen listings findes
if (listingIds.length === 0) {
console.log("🔍 No listings found. Getting object properties...");
try {
const propertiesResponse = await hubspotClient.crm.properties.coreApi.getAll(LISTINGS_OBJECT_TYPE_ID);
const associationProps = propertiesResponse?.results?.filter(p =>
p.name.includes('contact') || p.name.includes('association')
) ?? [];
console.log(`📋 Available contact properties:`, associationProps.map(p => p.name));
} catch (propsError) {
console.log("❌ Properties fetch failed:", propsError.message);
}
}
// LØSNING 3: Lav associations
console.log(`🔗 Creating associations...`);
// Associate deals
for (const dealId of dealIds) {
try {
await hubspotClient.crm.associations.v4.basicApi.create(
'tickets',
ticketId,
'deals',
dealId,
[{
associationCategory: 'HUBSPOT_DEFINED',
associationTypeId: 28
}]
);
console.log(`✅ Associated deal ${dealId}`);
} catch (assocError) {
console.log(`❌ Failed to associate deal ${dealId}:`, assocError.message);
}
}
// Associate listings - med omfattende debugging
for (const listingId of listingIds) {
console.log(`🔍 Attempting to associate listing ${listingId} with ticket ${ticketId}`);
// Først - tjek om listing faktisk eksisterer
try {
const listingCheck = await hubspotClient.crm.objects.basicApi.getById(LISTINGS_OBJECT_TYPE_ID, listingId);
console.log(`📋 Listing ${listingId} exists:`, listingCheck.id);
} catch (checkError) {
console.log(`❌ Listing ${listingId} doesn't exist:`, checkError.message);
continue;
}
// Find korrekte association type IDs for listings
try {
console.log(`🔍 Getting association types between tickets and ${LISTINGS_OBJECT_TYPE_ID}...`);
const assocTypes = await hubspotClient.crm.associations.schemaApi.getAll('tickets', LISTINGS_OBJECT_TYPE_ID);
console.log(`📋 Available association types:`, JSON.stringify(assocTypes?.results?.map(r => ({
id: r.typeId,
label: r.label,
name: r.name
})), null, 2));
// Brug den første tilgængelige association type
const validAssocType = assocTypes?.results?.[0]?.typeId || 1;
console.log(`🎯 Using association type ID: ${validAssocType}`);
// Prøv association med korrekt type
await hubspotClient.crm.associations.v4.basicApi.create(
'tickets',
ticketId,
LISTINGS_OBJECT_TYPE_ID,
listingId,
[{
associationCategory: 'HUBSPOT_DEFINED',
associationTypeId: validAssocType
}]
);
console.log(`✅ Associated listing ${listingId} with correct type ${validAssocType}`);
// Verificer association blev lavet
const verification = await hubspotClient.crm.associations.v4.basicApi.getPage(
'tickets',
ticketId,
LISTINGS_OBJECT_TYPE_ID
);
const associatedListings = verification?.results?.map(r => r.toObjectId) || [];
console.log(`🔍 Verification - ticket ${ticketId} is now associated with listings:`, associatedListings);
if (associatedListings.includes(listingId)) {
console.log(`✅ CONFIRMED: Listing ${listingId} is properly associated!`);
} else {
console.log(`❌ FAILED: Listing ${listingId} association not found in verification`);
}
} catch (schemaError) {
console.log(`❌ Schema fetch failed, trying manual approaches:`, schemaError.message);
// Fallback - prøv forskellige association approaches
const approaches = [
{
name: 'Batch API with generic type',
action: () => hubspotClient.crm.associations.batchApi.create('tickets', LISTINGS_OBJECT_TYPE_ID, {
inputs: [{
from: { id: ticketId },
to: { id: listingId },
type: 'ticket_to_listing'
}]
})
},
{
name: 'Direct API v3 style',
action: () => hubspotClient.apiRequest({
method: 'PUT',
path: `/crm/v3/objects/tickets/${ticketId}/associations/${LISTINGS_OBJECT_TYPE_ID}/${listingId}/ticket_to_listing`
})
},
{
name: 'Direct API v4 style',
action: () => hubspotClient.apiRequest({
method: 'PUT',
path: `/crm/v4/objects/tickets/${ticketId}/associations/${LISTINGS_OBJECT_TYPE_ID}/${listingId}`,
body: {
associationCategory: 'HUBSPOT_DEFINED',
associationTypeId: 1
}
})
}
];
for (const approach of approaches) {
try {
console.log(`🔄 Trying ${approach.name}...`);
await approach.action();
console.log(`✅ ${approach.name} succeeded`);
// Test verification igen
try {
const verification2 = await hubspotClient.crm.associations.v4.basicApi.getPage(
'tickets',
ticketId,
LISTINGS_OBJECT_TYPE_ID
);
const associatedListings2 = verification2?.results?.map(r => r.toObjectId) || [];
if (associatedListings2.includes(listingId)) {
console.log(`✅ CONFIRMED: ${approach.name} worked! Listing is associated.`);
break;
} else {
console.log(`❌ ${approach.name} didn't create visible association`);
}
} catch (verifyError) {
console.log(`❌ Verification failed after ${approach.name}`);
}
} catch (approachError) {
console.log(`❌ ${approach.name} failed:`, approachError.message);
}
}
}
}
const result = {
success: true,
associatedDeals: dealIds.length,
associatedListings: listingIds.length,
ticketId: ticketId
};
console.log(`✅ Final result:`, JSON.stringify(result));
return result;
} catch (err) {
console.error("🚫 Global Error:", err.message);
return {
success: false,
error: err.message
};
}
};