Last modified: August 22, 2025
Developers using the Calling SDK can now enable inbound calling within HubSpot. When a user receives and answers a call through your app in HubSpot, they can access call records directly in HubSpot, eliminating the need to switch back to the calling app. Calls are automatically logged in the Call Index Page where users can take real-time notes and review the call after it ends.

Requirements

Keep the following requirements in mind when building your app:
  1. Calling app appears inline within HubSpot: you want the users of your calling app to be anchored within HubSpot since it’s their primary workspace. To accomplish this, you’ll need to anchor the calling app to the HubSpot navigation bar via the calling remote.
  2. Calls are not interrupted by user navigation: users should be able to move between pages in HubSpot without losing an ongoing call. To avoid triggering a full page refresh when navigating in HubSpot, which would cause calls to drop, you can leverage the detached calling window, which will hold the call connection and maintain site navigation functionality.

Approaches to designing your app

The following approaches are available when designing the UI of your integration:

1. Use both the calling remote and the calling window

For a fully featured calling app experience, you can take this approach to ensure that your app appears in the calling remote, and the call connection is also held in the calling window. You’ll need to ensure that your integration synchronizes the state of the calling remote and the calling window. For a guide on how to implement this approach, check out the section on calling state synchronization.
both calling remote and call window

2. Use the calling remote only

With this approach, your calling app will appear in the calling remote, anchored to the HubSpot navigation bar. The main difference between this approach and the first approach is that your calling app must hold the connection using a desktop application or some other custom solution. If you don’t implement a custom solution to hold the connection, calls will still work but they’ll be disconnected if the user navigates to another HubSpot page. This is similar to the current drag-and-drop call widget experience.
calling remote only
If needed, you can take this alternate approach and only use the calling window in your app, and users will not be able to use the calling remote with your app. Additionally, incoming calls won’t appear in front of the currently focused browser window, and outbound calls will not bring your calling app into focus for the user.
calling window only
Please note:This approach will be sunset on August 31, 2025. Integrators should transition to a recommended approach before this date.

Configure your inbound calling integration

Follow the steps below to set up and configure your integration so users can receive inbound calls within HubSpot.

1. Configure your calling extension settings

Based on the approach you take for the calling experience of your app, update your app settings by making a PATCH request to /crm/v3/extensions/calling/{APP_ID}/settings?hapikey={DEVELOPER_ACCOUNT_API_KEY}, and provide the ID of your app and your developer account API key in the request.
curl --request PATCH \
 --url 'https://api.hubapi.com/crm/v3/extensions/calling/APP_ID/settings?hapikey=DEVELOPER_ACCOUNT_API_KEY' \
 --header 'accept: application/json' \
 --header 'content-type: application/json' \
 --data '{"supportsInboundCalling":true, "usesCallingWindow":true, "usesRemote":true}'
You can provide the following fields in your request to specify the approach your app is taking, as well as any other app details:
Field nameTypeDescription
supportsInboundCallingBooleanEnables inbound calling feature. The flag is set to false by default. This extension setting gates users to the new inbound calling experience and instead shows the drag and drop call widget in record pages for outbound calling.
usesCallingWindowBooleanDetermines whether the calling window is used.
usesRemoteBooleanDetermines whether the calling remote is used. Note that this field will be deprecated after Aug 31, 2025.
The values you provide for each of the fields above depend on the approach you take for designing your app.
  • If you’re using both the calling remote and calling window in your app, or if you’re just using the window-only approach, each of the values above should be set to true:
{
  ...otherExtensionSettings,
  "supportsInboundCalling": true,
  "usesCallingWindow": true,
  "usesRemote": true
}
  • If you decide to use the remote-only approach, you should only set the supportsInboundCalling and usesCallingWindow fields to true:
{
  ...otherExtensionSettings,
  "supportsInboundCalling": true,
  "usesCallingWindow": false,
  "usesRemote": true
}
For local development, you can either use local storage overrides to test changes or directly apply the settings using a developer test account without impacting production.

2. Install the latest version of Calling SDK

For npm, run:
npm i -s @hubspot/calling-extensions-sdk@latest
For yarn, run:
yarn add @hubspot/calling-extensions-sdk@latest

3. Set user availability

You must set the user’s availability using one of the following events:
  • Via the initialized event:
const payload = {
  // Optional: Whether a user is logged-in
  isLoggedIn: boolean,
  // Optional: Whether a user is available for inbound calling
  isAvailable: boolean,
  // Optional: The desired widget size
  sizeInfo: {
    height: number,
    width: number,
  },
};

extensions.initialized(payload);
  • Via the userAvailable event:
extensions.userAvailable();
  • Via the userUnavailable event:
extensions.userUnavailable();

4. Send message to notify HubSpot that an inbound call started

You will be able to send calling lifecycle events, such as callAnswered and callCompleted, in the same way it is done for outgoing calls.
const callInfo = {
  fromNumber: string, // Required: The caller's number
  toNumber: string, // Required: The recipient's number
  createEngagement: boolean, // Whether HubSpot should create an engagement for this call
};
extensions.incomingCall(callInfo);
  • If you’ve set createEngagement to true, you can subscribe to onCreateEngagementSucceeded and onCreateEngagementFailed. It is recommended you do this so that you can enable your calling app to support custom objects. This will allow future integration into other areas of HubSpot.
onCreateEngagementSucceeded(data) {
    const {
      /* A HubSpot created engagement id. */
      engagementId: number,
    } = data;
      ...
  }

onCreateEngagementFailed(data) {
    const {
      error: { message: string }
    } = data;
      ...
  }

5. Receive caller ID matches

  • You will be able to subscribe to onCallerIdMatchSucceeded and onCalledIdMatchFailed. This will enable you to receive contact matching data for the incoming call that previously had to be obtained via the Search API, and will solve its rate limitations.
onCallerIdMatchSucceeded: data => {
      /* HubSpot has fetched caller id matches for this call. */
    const {
      callerIdMatches: (ContactIdMatch | CompanyIdMatch)[];
    } = data;
    }

onCallerIdMatchFailed: data => {
      /* HubSpot has failed to fetch caller id matches for this call. */
    const {
      error: { message: string }
    } = data;
    }
type ObjectCoordinates = {
  portalId: number;
  objectTypeId: string;
  objectId: number;
}

type ContactIdMatch = {
  callerIdType: 'CONTACT';
  objectCoordinates: ObjectCoordinates;
  firstName: string;
  lastName: string;
  email: string;
}

type CompanyIdMatch = {
  callerIdType: 'COMPANY';
  objectCoordinates: ObjectCoordinates;
  name: string;
}

6. Navigate to a record page

Once you receive the caller ID matches, you can send HubSpot a message to navigate to a contact or company record page.
const data = {
  objectCoordinates: ObjectCoordinates, // from onCallerIdMatchSucceeded
};

extensions.navigateToRecord(data);
Once the call engagement is created, HubSpot will redirect to the contact page specified in the navigateToRecord payload and will sync with the SDK in the onReady event. You’ll need to re-initialize the SDK using the engagement ID and show an incoming call within the iframe.
// Receive an engagementId for an existing inbound call
type Payload = {
  engagementId: number | undefined
}

// Message indicating that HubSpot is ready to receive messages
onReady(payload) {
    // Send initialized message to HubSpot to indicate that the call widget is also ready
    extensions.initialized(payload);
    if (payload.engagementId) {
      // Initialize calling state in the app for existing inbound call
      ...
    }
    ...
}
In the following sections, preview how the incoming call feature will work in your calling app.

7. Set the provider

Before logging in to your calling app, you’ll need to select the provider from your call settings:
  • In your HubSpot account, click the settings icon in the main navigation bar.
  • In the left sidebar menu, click General. Then, click the Calling tab at the top.
  • Click the Make and receive calls through dropdown menu, then select your calling app.
Once the preferred provider is selected, incoming calls will only be received through the selected provider. HubSpot doesn’t support receiving incoming calls from multiple providers in this version. To switch providers, repeat the steps above to access the provider menu and select a different provider. If you’re switching from a provider that does not support inbound calling and still uses the drag-and-drop call widget for outbound calls, you can also find the Change provider button within the drag-and-drop call widget to access the provider menu. Calling remote change provider button:
Change provider button in the calling remote
Drag-and-drop widget change provider button:
Change provider button in the drag-and-drop call widget

8. Receive incoming calls

If you’ve not already set up an integration with any of the calling apps, learn more.
  • Log in to your calling app through the call widget in HubSpot. The call widget can be accessed on the main navigation bar.
  • Open calling window if calling window is used by your calling app.
Calling apps- make a call
  • Set availability to “Available” to start receiving calls.
Calling apps- unavailable
Calling apps- available
  • Answer inbound calls from the call remote.
Calling apps- answer
Please note:
  • The behavior may vary slightly based on each calling apps’ implementation.
  • If the call widget is minimized but you’re set to Available, you will still receive calls. If the call tab is closed during an ongoing call, the call will get disconnected.
Once the call is completed, the inbound call gets logged in the Call Index page. Missed calls will also get logged here. Once the above steps are complete, you can continue to extend the functionality of your calling app in HubSpot by setting up third-party calling in help desk (BETA).

Calling state synchronization

If you decide to take the first approach above and implement the calling remote and window functionality when building your app, calling apps will maintain their call connection in the calling window of your app. Your integration must still handle synchronizing state between the calling window and the calling remote. The setup section below outlines how to structure an onReady event handler to conditionally respond to different user events. You can review a full example in the calling-extensions-sdk repository.

Setup

When HubSpot is ready to receive messages, the onReady event handler will be called with a set of properties, including iframeLocation. You can use this property in your app to define conditions based on the value of iframeLocation:
  • iframeLocation = window: this means your calling app is hosted in the detached calling window. It’s recommended to handle the main calling logic in the calling window to maintain the call connection while navigating throughout HubSpot.
  • iframeLocation = remote: this indicates that your calling app is hosted in the anchored calling remote. It’s recommended that you use the remote to reflect the calling state happening in the calling window and rely on the calling window to handle the main calling logic. This ensures users can navigate freely within HubSpot without disconnecting their calls.
import CallingExtensions from "@hubspot/calling-extensions-sdk";

const options = {
 ...,
 eventHandlers: {
   ...,
   onReady: ({iframeLocation, ...restOfTheProperties}) => {
     /* HubSpot is ready to receive messages. */
     // Store the property to create different logic for different scenarios
     this.iframeLocation = iframeLocation
   },
 }
};

const extensions = new CallingExtensions(options);

Example

One option to keep instances of your calling application synchronized is to use the broadcast channel API. Based on whether the user is starting an outbound call from the remote or the calling window, you can structure your logic accordingly: When a user starts the outbound call from the remote:
  • The remote UI updates to display a ringing state.
  • The remote UI sends a broadcast message to the calling window.
  • When the calling window receives the broadcast message:
    • The calling window UI places the call based on your app’s custom routing logic.
    • The calling window UI updates to display a ringing state.
    • If necessary, the calling window uses any other methods from the calling extensions SDK to display additional updates (e.g., recording the call)
When a user starts the outbound call from the calling window:
  • The calling window UI places the call based on on your app’s custom routing logic.
  • The calling window UI updates to display a ringing state.
  • The calling window UI sends a broadcast message to the remote.
  • If necessary, the calling window uses the Calling Extensions SDK to communicate updates.
  • When the remote receives the broadcast message, the remote UI updates to display the ringing state.
The following code snippet provides an example of how you might handle the scenarios above.
private iframeLocation;
private extensions;

this.extensions = new CallingExtensions({
 eventHandlers: {
   ...,
   onReady: (data: {iframeLocation, ...restOfTheProperties}) => {
     this.iframeLocation = iframeLocation
   },
   ...
 },
});

// Create a BroadcastChannel to communicate between your app
this.broadcastChannel = new BroadcastChannel("calling-app-example");

this.broadcastChannel.onmessage = (data) => {
   switch(data.type) {
     case 'OUTGOING_CALL_STARTED': {
       if (this.iframeLocation === 'window') {
	  placeCall()
         this.extensions.outgoingCall({externalCallId: '123'})
       }
       handleUIChangeToDialing()
     }
     ...
   }
}

// When users start an outbound call, this function will be called
 handleOutgoingCall(callDetails: OnOutgoingCall)  {
   this.broadcastChannel.postMessage({
     type: thirdPartyToHostEvents.OUTGOING_CALL_STARTED,
     payload: callDetails,
   });
   if (this.iframeLocation === 'window') {
     placeCall()
     this.extensions.outgoingCall({externalCallId: '123'})
   }
   handleUIChangeToDialingState()
 }
You can review a full implementation of this logic and the broadcast channel handlers in the demo repository.