Clients question - Client (Calling) is creating a new integration with our platform

Hello team!

Adri from Hubspot Sales in here. I have a client who is trying to complete an integratin with our platform and is asking the fllowing questions:

  • To avoid requiring the operator to log into X Campaign (on 3C) every time, would it be best to create the app as a pop-up so they only need to log in once on the first access? Or is there a way for HubSpot to “store” the campaign and the operator’s token after the first login so they don’t need to log in again?
  • Does the @hubspot/calling-extensions-sdk automatically recognize the contact’s phone number if the app is opened in a pop-up? Or in that case, would the number need to be entered manually?
  • Another point: for some reason, if I minimize the app or leave it inactive for a while, it shows the message “Calls are offline” and I have to reopen it. Currently, I’m not using the calling extension SDK — just the application that makes calls through 3C. How can I fix this behavior?

He says "

By the way, I tested the default app from the documentation + GitHub — and in that one, you have to manually enter the client’s phone number; it doesn’t automatically detect it for dialing, even though it’s using the SDK.

I believe the calling-extensions-sdk would be useful in this case, right? So the operator wouldn’t have to manually type in the client’s number (not that it’s the end of the world if they have to, but still)."

Could the team please help me find the best next steps?

Hi, @APachecoDiaz :waving_hand: Thanks for your question.

Based on my read of the documentation:

  • the standard approach is for their server to securely store the user’s auth and refresh tokens from their “3C” service after the first login as HubSpot itself won’t store a third-party service’s token. But their app should be able to do this
  • double-check that they initialized the SDK and set up the event listeners correctly. The `sendOuboundCall` event is specifically what they’ll want to look into
  • implementing the SDK and its associated initialized and userLoggedIn messages will allow their app to correctly report its status

The best and fastest way for your client to get a solution is to have the developer who is building the integration post directly in this forum.

They’ll be able to:

  • share relevant (and non-sensitive) code snippets
  • describe their application’s architecture
  • provide specific error messages they’re seeing

Talk soon! — Jaycee

Hello! How are you? I’m Eduardo!

I’m currently developing a Click-to-Call solution, integrated with the 3C Plus <> Hubspot platform. To be honest, I’m still at the beginning of my journey as a developer. So far, I’ve managed to build an application that can place calls via 3C Plus and handle the returned events, updating the operator interface in real time.

Objective:

Integrate this application directly into HubSpot by leveraging the Calling Extensions SDK to initiate and end calls.

Current Project Structure:

components/click-to-call-system.tsx:

Contains the “core” of the application:
- Initiate calls
- Listen to and handle events from 3C Plus
- Update the operator interface based on the call status

lib/hubspot-call-provider.ts:
Responsible for initializing the HubSpot Calling Extensions SDK and mapping its events to internal handlers:

import CallingExtensions from “@hubspot/calling-extensions-sdk”

export interface HubspotProviderHandlers {
dial: (phone: string) => void
hangup: () => void
qualify: (qualificationId: string) => void
}

let hubspotInstance: CallingExtensions | null = null

export function initHubspotCallProvider(handlers: HubspotProviderHandlers) {
if (typeof window === “undefined”) return null
if (hubspotInstance) return hubspotInstance

hubspotInstance = new CallingExtensions({
debugMode: true,
eventHandlers: {
onReady: () => {
hubspotInstance?.initialized({})
console.log(“[HubSpot] SDK ready → 3C Plus”)
},
onDialNumber: (payload: any) => {
const number = payload?.toNumber || payload?.phoneNumber || payload?.number
if (number) handlers.dial(number)
},
onEndCall: () => {
handlers.hangup()
},
onCreateEngagementSucceeded: (data: any) => {
if (data?.callEndStatus) {
handlers.qualify(String(data.callEndStatus))
}
},
defaultEventHandler: (ev: any) => {
console.log(“[HubSpot] Event → 3C Plus”, ev)
},
},
})

;(window as any).HubSpotConversations = hubspotInstance
return hubspotInstance
}

At this point, the extension is already showing up in the HubSpot console, indicating it has been successfully initialized.

Next Steps:
I’d like to understand the ideal event flow moving forward:

Authentication — Should I emit an event like logged_in before starting the calls?

Call Handling — Can I directly invoke the 3C Plus API within click-to-call-system.tsx to trigger an outgoingCall event, or is there an additional handshake required?

GitHub Repository: GitHub - wosiak/clicktocall-3cplus-hubspot-v2 · GitHub

Looking forward to your guidance. Thank you very much!

Alright, so what were your adjustments?

I don’t recognize ‘dialingContext: onDialEventPayload’, but your outgoing call payload seems fine. From a glance, the event progression during the call seems good too. Do you also not get a call added to your records when the call is inbound? For inbound calls you should also get a CallerIdMatch Succeeded/Failed event from HubSpot.

As a test, try associating a call with one of your contacts/companies to see if the call will show up then:

You can find the toObjectId in the url of the contact/company you want to associate with. For contacts, associationTypeId is ‘194’, for companies ‘182’:

If that doesn’t work, then I’m afraid my experience ends there :confused:

Hey, @Wosiak :waving_hand: It looks like @JeroenCloudCTI has done an impressive job trying to help pinpoint potential issues. Of all the development paths, the calling SDK set up seems to be one of the more challenging due to the number of variables you have to contend with.

One more troubleshooting question — When the `onDialNumber` event fires, are you saving the entire event payload and passing it back in the `dialingContext` property when you send the `OUTGOING_CALL_STARTED` message?

Thanks! — Jaycee

Good morning, @JeroenCloudCTI and @Jaycee_Lewis ! How are you?

That was exactly it! I just needed to pass the full object received inside dialingContext into outgoingCallData.

Now the call is being successfully logged.

I’m still working on a few other things, but I believe they’re just minor details!

And just to clarify: the goal is to make my app public (available to any HubSpot user).

Can it be public even if it’s opened in a calling window? Or does it necessarily have to be a widget?

The setting for making your app a widget or windowed and the setting for making your app public or private are unrelated, so you can have a public app in a calling window​:+1:

Hello, @Jaycee_Lewis ! How are you? I’m Eduardo!

I’m currently developing a Click-to-Call solution, integrated with the 3C Plus <> Hubspot platform. To be honest, I’m still at the beginning of my journey as a developer. So far, I’ve managed to build an application that can place calls via 3C Plus and handle the returned events, updating the operator interface in real time.

Objective:

Integrate this application directly into HubSpot by leveraging the Calling Extensions SDK to initiate and end calls.

Current Project Structure:

components/click-to-call-system.tsx:

Contains the “core” of the application:
- Initiate calls
- Listen to and handle events from 3C Plus
- Update the operator interface based on the call status

lib/hubspot-call-provider.ts:
Responsible for initializing the HubSpot Calling Extensions SDK and mapping its events to internal handlers:

import CallingExtensions from “@hubspot/calling-extensions-sdk”

export interface HubspotProviderHandlers {
dial: (phone: string) => void
hangup: () => void
qualify: (qualificationId: string) => void
}

let hubspotInstance: CallingExtensions | null = null

export function initHubspotCallProvider(handlers: HubspotProviderHandlers) {
if (typeof window === “undefined”) return null
if (hubspotInstance) return hubspotInstance

hubspotInstance = new CallingExtensions({
debugMode: true,
eventHandlers: {
onReady: () => {
hubspotInstance?.initialized({})
console.log(“[HubSpot] SDK ready → 3C Plus”)
},
onDialNumber: (payload: any) => {
const number = payload?.toNumber || payload?.phoneNumber || payload?.number
if (number) handlers.dial(number)
},
onEndCall: () => {
handlers.hangup()
},
onCreateEngagementSucceeded: (data: any) => {
if (data?.callEndStatus) {
handlers.qualify(String(data.callEndStatus))
}
},
defaultEventHandler: (ev: any) => {
console.log(“[HubSpot] Event → 3C Plus”, ev)
},
},
})

;(window as any).HubSpotConversations = hubspotInstance
return hubspotInstance
}

At this point, the extension is already showing up in the HubSpot console, indicating it has been successfully initialized.

Next Steps:
I’d like to understand the ideal event flow moving forward:

Authentication — Should I emit an event like logged_in before starting the calls?

Call Handling — Can I directly invoke the 3C Plus API within click-to-call-system.tsx to trigger an outgoingCall event, or is there an additional handshake required?

GitHub Repository: GitHub - wosiak/clicktocall-3cplus-hubspot-v2 · GitHub

Looking forward to your guidance. Thank you very much!

Hi @Wosiak :waving_hand: Welcome to the party. Our community is peer-to-peer based and filled with a brilliant and diverse group of folks. Hey @JeroenCloudCTI @DilionSmith @himanshurauthan, do you have any specific advice for @Wosiak? Or if not, is there anything in-general about app development with HubSpot that you can share?

Thank you very much! — Jaycee

Hi @Wosiak ,

From my experience, it’s necessary to emit the ‘user_logged_in’ and ‘user_available’ events before calling, otherwise Hubspot will not reply to your call control events and no engagements will be created! The logged in and available events only have to be emitted once on app startup, not before every call.
With those two events fired beforehand, you should be able to directly emit an outgoingCall event alongside your 3C Plus API. To try to also fire a CallEnded event too, at some point. If your 3C API does not register call ends, just invoke the CallEnded a second or so after the CallStarted event, that wraps up the engagement on the HubSpot side while you can continue calling with 3C.

Hello, @JeroenCloudCTI and @Jaycee_Lewis! How are you guys? Thank you so much for your help! Actually, I had already managed to make calls through my app, but I’m facing another issue now when it comes to logging those calls in HubSpot.

  • If my app is loaded inside HubSpot as a widget, the call is logged successfully.
  • If it’s opened in a new tab, the call isn’t logged.

Keep in mind it’s the exact same code in both cases. The only difference is that one is opened as a widget and the other isn’t.

GitHub repository: GitHub - wosiak/clicktocall-3cplus-hubspot-v2 · GitHub
The code files that matter most are lib/hubspot-call-provider and components/click-to-call-system.
The same code is used whether the app runs as a widget or not.

Does HubSpot prevent the call record from being created when the app is opened in an external tab?

Important: My app needs to be opened in an external tab to prevent the user from being logged out of 3C Plus (our telephony system).

@DilionSmith and @himanshurauthan too! :slightly_smiling_face:

@Wosiak one difference between the widget and a separate tab is that the hubspot site can start up the widget in the background before you open it, giving it more time to initialise.

Did you clear your browser console shortly before taking the first screenshot (the one with the tab)? The console history seems quite short, and I don’t see a LOGGED_IN nor USER_AVAILABLE featured in it. If they weren’t called beforehand, the engagements for made calls are not made.

I see you call the userLoggedIn and userAvailable in the SDK Ready function, could it be that these lines weren’t hit yet when you took the screenshot?

Hello @JeroenCloudCTI , how are you?

My application is notifying HubSpot that the user is logged_in and then notifying user_available. Is that correct?

I’ve noticed that when I use the app via the calling window, after the notifications it ends up in an array and the blue message doesn’t appear (which is what happens when I open the app as a widget).

Another point:

I could use the widget just fine, yes! But only if there were a way for it not to close when the user navigates away from the current screen. It would need to stay minimized or something like that (not fully closed, otherwise the user would have to log in again every time).

Yes, it seems like notifying HubSpot of login status and availability is working as intended.

I’m not quite following your second point: what ends up in an array? In both screenshots, the blue messages are part of an array, the widget ones have just not been expanded in the console.

As for you last point, the widget stays active when the tab it’s in is not selected. However, only the widget in the tab where you press to call a contact/company will receive a DIAL_NUMBER HubSpot message. I’m not sure how that works with the window variant, I guess that one should always receive the dial message.

Side question: why would users need to log in again when the app is closed? You can use the localstorage to save if someone is logged in or not!

Hello, @JeroenCloudCTI! I made some adjustments, and the errors no longer appear in the console.

Now, no errors are shown, but the call still isn’t being logged in HubSpot.

Here’s the console flow:

1- to HubSpot: OUTGOING_CALL_STARTED
I notify with createEngagement: true.
I’m not passing dialingContext: onDialEventPayload in the request—could that be the issue? If I should include it, what format should it have?

2- from HubSpot: INITIATE_CALL_ID_SUCCEEDED
I receive the callId.

3- from HubSpot: ENGAGEMENT_CREATED
I receive the engagementId.

4- from HubSpot: CREATE_ENGAGEMENT_SUCCEEDED
Example payload:
{
“callDirection”: “OUTBOUND”,
“engagementId”: 82087178010,
“objectId”: 0,
“objectTypeId”: “”, // It seems odd to receive “” and 0 for these
“ownerId”: 78930248
}

5- to HubSpot: CALL_ENDED
Request I send:
{
“callEndStatus”: “COMPLETED”,
“engagementId”: 82087178010,
“externalCallId”: “ZQfIWCynNQ”
}

6- to HubSpot: CALL_COMPLETED
Request I send:
{
“data”: {
“body”: “Call qualified as: Sale made by phone”
},
“engagementId”: 82087178010,
“engagementProperties”: {
“hs_call_status”: “COMPLETED”,
“externalCallId”: “ZQfIWCynNQ”
},
“subject”: “Call - 5542999601808”
}

7- from HubSpot: Updated call status to COMPLETED for callId 1657175891

8- from HubSpot: UPDATE_ENGAGEMENT_SUCCEEDED
{
“data”: {}
}
// It seems odd that the data object is empty here

Evidence is attached. I’m reviewing what I can tweak to get it working 100%—it’s trickier now since there’s no error pointing to what’s wrong! :sweat_smile:

Important: I’m using the app via the Calling Window.
Answering your last side question: 3C Plus requires the user to be logged into our platform to make each call. When the user closes the widget (navigates away in the Hub), they get logged out, which would force them to log in again.
Previously, errors appeared in the Calling Window console; now no errors are displayed visually.