diff --git a/packages/core/src/destination-kit/index.ts b/packages/core/src/destination-kit/index.ts index 99473fe05b8..2d82a87e402 100644 --- a/packages/core/src/destination-kit/index.ts +++ b/packages/core/src/destination-kit/index.ts @@ -239,6 +239,7 @@ interface AuthSettings { interface RefreshAuthSettings { settings: Settings auth: OAuth2ClientCredentials + features?: Features } interface Authentication { @@ -564,7 +565,8 @@ export class Destination { async refreshAccessToken( settings: Settings, oauthData: OAuth2ClientCredentials, - synchronizeRefreshAccessToken?: () => Promise + synchronizeRefreshAccessToken?: () => Promise, + features?: Features ): Promise { if (!(this.authentication?.scheme === 'oauth2' || this.authentication?.scheme === 'oauth-managed')) { throw new IntegrationError( @@ -590,7 +592,7 @@ export class Destination { // Invoke synchronizeRefreshAccessToken handler if synchronizeRefreshAccessToken option is passed. // This will ensure that there is only one active refresh happening at a time. await synchronizeRefreshAccessToken?.() - return this.authentication.refreshAccessToken(requestClient, { settings, auth: oauthData }) + return this.authentication.refreshAccessToken(requestClient, { settings, auth: oauthData, features }) } private partnerAction( @@ -1062,7 +1064,8 @@ export class Destination { const newTokens = await this.refreshAccessToken( destinationSettings, oauthSettings, - options?.synchronizeRefreshAccessToken + options?.synchronizeRefreshAccessToken, + options?.features ) if (!newTokens) { diff --git a/packages/destination-actions/src/destinations/hubspot/__tests__/index.test.ts b/packages/destination-actions/src/destinations/hubspot/__tests__/index.test.ts index 15b85a96bf0..b72292d2cff 100644 --- a/packages/destination-actions/src/destinations/hubspot/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/hubspot/__tests__/index.test.ts @@ -5,6 +5,13 @@ import { HUBSPOT_BASE_URL } from '../properties' const testDestination = createTestIntegration(Definition) +const oauthData = { + refreshToken: 'refresh-token', + accessToken: 'access-token', + clientId: 'client-id', + clientSecret: 'client-secret' +} + describe('HubSpot Cloud Mode (Actions)', () => { describe('testAuthentication', () => { it('should validate authentication inputs', async () => { @@ -23,4 +30,37 @@ describe('HubSpot Cloud Mode (Actions)', () => { ) }) }) + + describe('refreshAccessToken', () => { + it('should use v1 endpoint when feature flag is not set', async () => { + nock(HUBSPOT_BASE_URL) + .post('/oauth/v1/token') + .reply(200, { access_token: 'new-access-token' }) + + const result = await testDestination.refreshAccessToken({}, oauthData) + expect(result).toEqual({ accessToken: 'new-access-token' }) + }) + + it('should use v1 endpoint when feature flag is false', async () => { + nock(HUBSPOT_BASE_URL) + .post('/oauth/v1/token') + .reply(200, { access_token: 'new-access-token' }) + + const result = await testDestination.refreshAccessToken({}, oauthData, undefined, { + 'actions-hubspot-oauth-v2': false + }) + expect(result).toEqual({ accessToken: 'new-access-token' }) + }) + + it('should use 2026-03 endpoint when feature flag is enabled', async () => { + nock(HUBSPOT_BASE_URL) + .post('/oauth/2026-03/token') + .reply(200, { access_token: 'new-access-token-v2' }) + + const result = await testDestination.refreshAccessToken({}, oauthData, undefined, { + 'actions-hubspot-oauth-v2': true + }) + expect(result).toEqual({ accessToken: 'new-access-token-v2' }) + }) + }) }) diff --git a/packages/destination-actions/src/destinations/hubspot/index.ts b/packages/destination-actions/src/destinations/hubspot/index.ts index b51276ff2f7..fe0213498e3 100644 --- a/packages/destination-actions/src/destinations/hubspot/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/index.ts @@ -8,7 +8,7 @@ import upsertCustomObjectRecord from './upsertCustomObjectRecord' import upsertObject from './upsertObject' import customEvent from './customEvent' import { HUBSPOT_BASE_URL } from './properties' -import { HUBSPOT_CRM_API_VERSION, HUBSPOT_OAUTH_API_VERSION } from './versioning-info' +import { HUBSPOT_CRM_API_VERSION, HUBSPOT_OAUTH_API_VERSION, HUBSPOT_OAUTH_API_VERSION_NEXT_FLAGON } from './versioning-info' interface RefreshTokenResponse { access_token: string } @@ -31,9 +31,9 @@ const destination: DestinationDefinition = { // HubSpot doesn't have a test authentication endpoint, so we using a lightweight CRM API to validate access token return request(`${HUBSPOT_BASE_URL}/crm/${HUBSPOT_CRM_API_VERSION}/objects/contacts?limit=1`) }, - refreshAccessToken: async (request, { auth }) => { - // Return a request that refreshes the access_token if the API supports it - const res = await request(`${HUBSPOT_BASE_URL}/oauth/${HUBSPOT_OAUTH_API_VERSION}/token`, { + refreshAccessToken: async (request, { auth, features }) => { + const oauthVersion = features?.['actions-hubspot-oauth-v2'] ? HUBSPOT_OAUTH_API_VERSION_NEXT_FLAGON : HUBSPOT_OAUTH_API_VERSION + const res = await request(`${HUBSPOT_BASE_URL}/oauth/${oauthVersion}/token`, { method: 'POST', body: new URLSearchParams({ refresh_token: auth.refreshToken, diff --git a/packages/destination-actions/src/destinations/hubspot/versioning-info.ts b/packages/destination-actions/src/destinations/hubspot/versioning-info.ts index dd78826dd16..f8bd773babe 100644 --- a/packages/destination-actions/src/destinations/hubspot/versioning-info.ts +++ b/packages/destination-actions/src/destinations/hubspot/versioning-info.ts @@ -11,7 +11,13 @@ export const HUBSPOT_CRM_API_VERSION = 'v3' export const HUBSPOT_CRM_ASSOCIATIONS_API_VERSION = 'v4' /** HUBSPOT_OAUTH_API_VERSION - * HubSpot OAuth API version. + * HubSpot OAuth API version (legacy). * API reference: https://developers.hubspot.com/docs/api-reference/auth-oauth-v1/tokens/post-oauth-v1-token */ export const HUBSPOT_OAUTH_API_VERSION = 'v1' + +/** HUBSPOT_OAUTH_API_VERSION_NEXT_FLAGON + * HubSpot OAuth API version (2026-03). + * API reference: https://developers.hubspot.com/docs/api-reference/latest/authentication/manage-oauth-tokens + */ +export const HUBSPOT_OAUTH_API_VERSION_NEXT_FLAGON = '2026-03'