Populating HubSpot call type when making calls with AirCall

Hey everyone,

Is it possible to automatically populate the type of a call in HubSpot when making calls with AirCall? For the call outcome ths works just fine but this functionality doesn’t seem to extend to the call type.

AirCall tags are being passed on – but in a different section of the activity. They’re not helpful in this case, the call outcome and call type are needed for custom HubSpot activity reports.

I’m referring to the call type as explained here and shown in the screenshot under “Type”.

It seems like this isn’t possible at this stage – if other AirCall users could confirm or share their workarounds, that’d be amazing.

Thanks in advance!

Hi @karstenkoehler,

Great question, thank you for posting! I wanted to tag a few Aircall users who might have found a workaround: @StefanWendt, @EmilyWade, @TomBuchanan, @asknick do you use the Call Type activity property when logging Aircall calls in HubSpot?

Thanks

Mia

Hi @karstenkoehler, I will be hosting an AMA around calling feature especially with AirCall, maybe we can pick that topic up. Will have a closer look into it next week.

Best, Stefan

Nice, @StefanWendt, looking forward to it!

Hi @karstenkoehler ,

as just answered in the AMA about calling feature and phone-integrations (german) a few minutes ago, here a possible solution:

The integration between Aircall and HubSpot automatically names the type of call and distinguishes between 1. Inbound Call and 2. Outbound Call. In addition, the information is transmitted whether it is an “Answered” or an “Un-answered” call. You can see the respective call type directly in the contact history (see example image)

Alternatively, you could always assign individual tags to the call during the call (of course only if you do not miss the call or take action yourself) to provide information on whether it was a conference, for example, a discovery call, a demo call, a decision-maker call or similar. These aircall tags will be synced via the API and are afterwards visible in the contact history and especially useful for automation workflows :wink:

Hope that helps! :slightly_smiling_face:

Best, Stefan

Thank you @StefanWendt!

So to confirm, it’s not possible to have AirCall populate the “official” HubSpot call type field then, correct? How would you go about reporting on call types by call outcome?

Thanks again!

Correct @karstenkoehler at the moment there is no mapping between Aircall and HubSpot properties possible. Call outcome should be done via custom property in HubSpot so its available for a custom report which wouldn’t be possible just with the notes..

Thanks for confirming, @StefanWendt!

Hi @StefanWendt,

Are you aware of any updates or new ways to approach this by any chance? (Custom code would be acceptable.)

Best regards!

Any news on this? I’ve been trying to solve it for a while.

Hey @karstenkoehler,

I have not found a custom code solution to this problem. It is a little code extensive, but it seems to be working pretty well.

I am using the HubSpot API to retrieve the “Call Notes” extracting the AirCall tags within and then updating the “Call Type” based on the tags. There are a couple of “issues” with this, which I will describe in more detail below:
1. The code does not have a trigger so I am just running it once pr. 24 hours. For me, this is fine since I am using the type for reporting and not any immediate trigger actions.

2. The retrieval of calls with the HubSpot API does not allow calls done in a specific timeframe and will always start its query from the oldest records. This means that as you get more calls in your CRM, the script will take longer and longer to run. However, there is the ability to provide an offset to the query by providing the ID of the call you want to start with; this solves the problem. However, this introduces the issue of needing to store this offset value and updating it every time the script is run. The code provided below contains some pseudo code for this, which needs to be updated to fit the environment you are running it in. I am personally running this in Zapier, where I am using “Storage by Zapier” to keep track of it.

Code:

import requests
import json
import re
from datetime import datetime, timedelta

# HubSpot API Access Token
ACCESS_TOKEN = "YOUR ACCESS TOKEN HERE"

# Headers with authorization token
HEADERS = {
 'Authorization': f'Bearer {ACCESS_TOKEN}',
 'Content-Type': 'application/json'
}

# HubSpot API Endpoints
CALLS_URL = 'https://api.hubapi.com/crm/v3/objects/calls'
UPDATE_CALL_URL = 'https://api.hubapi.com/crm/v3/objects/calls/{call_id}'
CALL_ID_OFFSET = 70202354556 # Offset 

# Define your tag-to-call-type mapping
TAG_TO_TYPE_MAPPING = {
 'prospect': 'Prospect',
 'onboarding': 'Onboarding',
 'checkin': 'Check In',
 'support call': 'Tech Support',
 'testimonial': 'Testimonial',
 'late checkin': 'Late Check In',
}

def save_after_token(after_token):
 """
 Save the after token in Zapier Storage for the next execution.
 """
 data = {"after": after_token}
 response = requests.post(CALL_ID_OFFSET, json=data)
 if response.status_code != 200:
 print(f"Error saving after token: {response.status_code}, {response.text}")

def load_after_token():
 """
 Load the after token from Zapier Storage.
 """
 response = requests.get(CALL_ID_OFFSET)
 if response.status_code == 200:
 data = response.json()
 return data.get("after", None)
 return None

def fetch_calls():
 """
 Fetch calls from HubSpot, including call notes (hs_call_body), resuming from the last saved 'after' token if available.

 Returns:
 list: A list of call records.
 """
 params = {
 'limit': 100, # Max records per request
 'properties': ['hs_call_disposition', 'hs_timestamp', 'hs_call_title', 'hs_call_body'],
 'associations': 'contacts'
 }

 after = 70202354556 #load_after_token()
 all_calls = []

 while True:
 if after:
 params['after'] = after

 response = requests.get(CALLS_URL, headers=HEADERS, params=params)

 if response.status_code == 200:
 data = response.json()
 results = data.get('results', [])
 after = data.get('paging', {}).get('next', {}).get('after')

 for call in results:
 all_calls.append({
 'id': call['id'],
 'timestamp': call['properties'].get('hs_timestamp'),
 'subject': call['properties'].get('hs_call_title', 'No title provided'),
 'notes': call['properties'].get('hs_call_body', ''), # Retrieve HTML call notes
 'contact_ids': call.get('associations', {}).get('contacts', [])
 })

 if after:
 save_after_token(after)

 if not after:
 break
 else:
 print(f"Error fetching calls: {response.status_code}, {response.text}")
 break

 return all_calls

def determine_call_type(notes):
 """
 Determine the call type based on extracted tags.

 Args:
 tags (list): The list of tags found in the call notes.

 Returns:
 str: The corresponding call type, or None if no match.
 """
 for tag in TAG_TO_TYPE_MAPPING.keys():
 if tag in notes:
 return tag
 return None

def update_call_type(call_id, new_type):
 """
 Update the call type in HubSpot.

 Args:
 call_id (str): The HubSpot call ID.
 new_type (str): The new call type.

 Returns:
 bool: True if the update was successful, False otherwise.
 """
 url = UPDATE_CALL_URL.format(call_id=call_id)
 data = {
 "properties": {
 "hs_activity_type": new_type
 }
 }

 response = requests.patch(url, headers=HEADERS, json=data)

 if response.status_code == 200:
 return True
 else:
 print(f"Failed to update call {call_id}: {response.status_code}, {response.text}")
 return False

def process_calls():
 """
 Main function to fetch calls, analyze tags, and update call types.
 """
 calls = fetch_calls()
 print(f"Fetched {len(calls)} calls from the last run.")

 for call in calls:
 tag = determine_call_type(call["notes"])
 if tag:
 print(f"Updating Call ID {call['id']} to Type: {tag}")
 update_call_type(call['id'], tag)
 else:
 print(f"No matching call type for Call ID {call['id']}. Skipping.")

# Run in once pr. day wherever you host your code (I use zapier, which can also store my offset value)
if __name__ == '__main__':
 process_calls()

I hope this helps, and if you have any questions, feel free to reach out.

Best,

Christopher Ravn Boe Jensen

Head of RevOps at OptoCeutics

Hey @karstenkoehler,

I have found a custom code solution to this problem. It is a little code extensive, but it seems to be working pretty well.

I am using the HubSpot API to retrieve the “Call Notes” extracting the AirCall tags within and then updating the “Call Type” based on the tags. There are a couple of “issues” with this, which I will describe in more detail below:
1. The code does not have a trigger so I am just running it once pr. 24 hours. For me, this is fine since I am using the type for reporting and not any immediate trigger actions.

2. The retrieval of calls with the HubSpot API does not allow calls done in a specific timeframe and will always start its query from the oldest records. This means that as you get more calls in your CRM, the script will take longer and longer to run. However, there is the ability to provide an offset to the query by providing the ID of the call you want to start with; this solves the problem. However, this introduces the issue of needing to store this offset value and updating it every time the script is run. The code provided below contains some pseudo code for this, which needs to be updated to fit the environment you are running it in. I am personally running this in Zapier, where I am using “Storage by Zapier” to keep track of it.

Code:

import requests
import json
import re
from datetime import datetime, timedelta

# HubSpot API Access Token (Use Zapier's Secrets for security)
ACCESS_TOKEN = "YOUR ACCESS TOKEN HERE"

# Headers with authorization token
HEADERS = {
 'Authorization': f'Bearer {ACCESS_TOKEN}',
 'Content-Type': 'application/json'
}

# HubSpot API Endpoints
CALLS_URL = 'https://api.hubapi.com/crm/v3/objects/calls'
UPDATE_CALL_URL = 'https://api.hubapi.com/crm/v3/objects/calls/{call_id}'
CALL_ID_OFFSET = 70202354556 # Offset 

# Define your tag-to-call-type mapping
TAG_TO_TYPE_MAPPING = {
 'prospect': 'Prospect',
 'onboarding': 'Onboarding',
 'checkin': 'Check In',
 'support call': 'Tech Support',
 'testimonial': 'Testimonial',
 'late checkin': 'Late Check In',
}

def save_after_token(after_token):
 """
 Save the after token in Zapier Storage for the next execution.
 """
 data = {"after": after_token}
 response = requests.post(CALL_ID_OFFSET, json=data)
 if response.status_code != 200:
 print(f"Error saving after token: {response.status_code}, {response.text}")

def load_after_token():
 """
 Load the after token from Zapier Storage.
 """
 response = requests.get(CALL_ID_OFFSET)
 if response.status_code == 200:
 data = response.json()
 return data.get("after", None)
 return None

def fetch_calls():
 """
 Fetch calls from HubSpot, including call notes (hs_call_body), resuming from the last saved 'after' token if available.

 Returns:
 list: A list of call records.
 """
 params = {
 'limit': 100, # Max records per request
 'properties': ['hs_call_disposition', 'hs_timestamp', 'hs_call_title', 'hs_call_body'],
 'associations': 'contacts'
 }

 after = 70202354556 #load_after_token()
 all_calls = []

 while True:
 if after:
 params['after'] = after

 response = requests.get(CALLS_URL, headers=HEADERS, params=params)

 if response.status_code == 200:
 data = response.json()
 results = data.get('results', [])
 after = data.get('paging', {}).get('next', {}).get('after')

 for call in results:
 all_calls.append({
 'id': call['id'],
 'timestamp': call['properties'].get('hs_timestamp'),
 'subject': call['properties'].get('hs_call_title', 'No title provided'),
 'notes': call['properties'].get('hs_call_body', ''), # Retrieve HTML call notes
 'contact_ids': call.get('associations', {}).get('contacts', [])
 })

 if after:
 save_after_token(after)

 if not after:
 break
 else:
 print(f"Error fetching calls: {response.status_code}, {response.text}")
 break

 return all_calls

def determine_call_type(notes):
 """
 Determine the call type based on extracted tags.

 Args:
 tags (list): The list of tags found in the call notes.

 Returns:
 str: The corresponding call type, or None if no match.
 """
 for tag in TAG_TO_TYPE_MAPPING.keys():
 if tag in notes:
 return TAG_TO_TYPE_MAPPING[tag]

 return None

def update_call_type(call_id, new_type):
 """
 Update the call type in HubSpot.

 Args:
 call_id (str): The HubSpot call ID.
 new_type (str): The new call type.

 Returns:
 bool: True if the update was successful, False otherwise.
 """
 url = UPDATE_CALL_URL.format(call_id=call_id)
 data = {
 "properties": {
 "hs_activity_type": new_type
 }
 }

 response = requests.patch(url, headers=HEADERS, json=data)

 if response.status_code == 200:
 return True
 else:
 print(f"Failed to update call {call_id}: {response.status_code}, {response.text}")
 return False

def process_calls():
 """
 Main function to fetch calls, analyze tags, and update call types.
 """
 calls = fetch_calls()
 print(f"Fetched {len(calls)} calls from the last run.")

 for call in calls:
 tag = determine_call_type(call["notes"])
 if tag:
 print(f"Updating Call ID {call['id']} to Type: {tag}")
 update_call_type(call['id'], tag)
 else:
 print(f"No matching call type for Call ID {call['id']}. Skipping.")

# Run in once pr. day wherever you host your code (I use zapier, which can also store my offset value)
if __name__ == '__main__':
 process_calls()

Hope this helps. Let me know if you have any questions

Best,

Christopher Ravn Boe Jensen

Head of RevOps at OptoCeutics

I built a new way to log call types while in Aircall without needing to code anything! I added my HubSpot call types as tags in Aircall. I used a different color from my outcomes and each team got their own color for their call types. In HubSpot I made a call-based workflow. I made a group of enrollment criteria for each call type like in the first picture then I did a branch below that for each call type (picture 2).

I did an edit property action after each branch to set the Call Type accordingly.

I made call tagging required in Aircall so after my people finish a call in Aircall they select a tag for their Call outcome and a tag for their Call type. The call outcome is set based on the Aircall <> HubSpot integration settings and now my call types are set by the HubSpot workflow! :tada: And now my team doesn’t have to leave Aircall to log call types or outcomes and I can just make sure to train them if they’re not logging one or the other correctly.

Thank you for sharing @KMace3! — Jaycee

How do you manage to update the last call, only ?

Because it’s a call workflow it enrolls specific calls. If you do a contact workflow you run into the problem of not being able to select which call specifically to update.

this looks good, are you by chance assigning the tags in aircall manually? that’s where i am stuck trying to avoid creating a webhook to automate that. I just want to see the missed call reason in hubspot.

Hi @DFlynn41 and welcome, we are delighted to have you here!
Thanks for reaching out to the HubSpot Community!
I understand that you are using Aircall and you’d like to see the missed call reason sync in HubSpot.
Hi @KMace3, can you share with @DFlynn41 how you add the tags in Aircall, please?
Have a lovely day and thanks so much!
Bérangère

Yes, my users are selecting the tags in Aircall manually. When they finish a call they select a tag for the call outcome and the call type.

I’m not familar with missed call reasons - is that a feature in Aircall? If you’re using a tag in Aircall for this my workflow will work for you.

  1. Create a tag in Aircall for the missed call reason
  2. Create a call workflow in HubSpot that looks for the tag you created in Aircall in a call’s notes (that’s where the tags show up in HubSpot)
  3. Use an edit action in the workflow to populate the data you’re wanting in HubSpot

Yeah, missed call reason in aircall will show that an incoming missed call is due to things like outside available hours, no agent available, dropped call, etc. This isn’t pulled into hubspot directly but I know I can if I associate all calls with this tag, but can’t reasonably get people to manually assign tags on calls they’ve missed. There is a way to do it automatically but you need a webhook. I was hoping to find a work around that didn’t require that since Aircall/HubSpot don’t seem to believe this is a data point it should just be pushing in (which I vehemently disagree with).