I've tried with this documentation authenticate and with this REST to send the conversion over REST API due to missing Typescript/Javascript-Client-Bib.
I am already failing at the authentication. I hope, if the authentication is working, the click conversion will be send. For the case I am guessing wrong, I extended the question. I hope the question is not to big. Blame me if I am wrong.
This is my error: 
My setup Regarding to the documentation I have a google ads account with a developer token. This token will be used, when I send the click conversion, as you can see here. The token has nothing to do with the authentication of the service account. Therefore I have a service account on the Google Cloud Project, which also has the Google Ads Api enabled. 
I also added to the workspace domain to the domain wide delegation the client id of the service account with the scope https://www.googleapis.com/auth/adwords
This created in the category APIs & Services also an OAuth 2.0 Client IDs
The clientIds are fitting as well.
Just in case I also added an OAuth consent screen with the scope https://www.googleapis.com/auth/adwords with the Testing status and external. But I don't think I will need this with a service account.
The service account itself has no further related rights. The documentation don't give me the info, that the service account need further rights. My thoughts: I added the client id to the domain wide delegation, this should be enough. I hope I am wrong here.
Now everything should be set up. I hope I didn't miss a step.
My guess: Either I am missing some rights. Or I missunderstand the refresh token in the function authenticateToGoogleAdsManager. I create a signed JWT. Google says here, I need a refresh token. But the authentication via await fetch('https://oauth2.googleapis.com/token'
just gives me an access token. So I thought I just need a jwt here.
This is the way I am executing my code (in a testcase. Service Account JSON and clickConversion are given.)
// First I create a signed jwt const jwt = generateJsonWebTokenForServiceAccount( serviceAccount, ['https://www.googleapis.com/auth/adwords'], 'googleads' ) // Then I use the signed jwt to authenticate to Google Ads Manager const authenticationResult = await authenticateToGoogleAdsManager( serviceAccount.client_id, serviceAccount.private_key, jwt ) console.log(authenticationResult) // Then I use the access token to send a click conversion to Google Ads Manager const test = await sendClickConversionToGoogleAdsManager( CUSTOMERID, clickConversion, accessToken.access_token, 'DEV-TOKEN' )
Here are my functions:
/** * Generates a JSON Web Token (JWT) for a service account. * * @param serviceAccount - The service account object containing the client email and private key. * @param scopes - An array of scopes for which the token will be authorized. * @param serviceName - The name of the service for which the token will be authorized. Default is 'oauth2'. * @param expirationTimeInSeconds - The expiration time of the token in seconds. Default is 3600 seconds (1 hour). * @returns The generated JSON Web Token. */ export function generateJsonWebTokenForServiceAccount(serviceAccount: ServiceAccount, scopes: string[], serviceName: string = 'oauth2', expirationTimeInSeconds = 3600) { const aud = serviceName === 'oauth2' ? 'https://oauth2.googleapis.com/token' : `https://${serviceName}.googleapis.com/` const currentTimestamp = Math.floor(Date.now() / 1000) const expirationTimestamp = currentTimestamp + expirationTimeInSeconds const payload = { iss: serviceAccount.client_email, sub: serviceAccount.client_email, scope: scopes.join(' '), aud: aud, exp: expirationTimestamp, iat: currentTimestamp } const options: SignOptions = { algorithm: 'RS256' } return jwt.sign(payload, serviceAccount.private_key, options) } /** * Authenticates to Google Ads Manager using the provided credentials. * @param clientId The client ID for authentication. * @param clientSecret The client secret for authentication. * @param refreshToken The refresh token for authentication. * @returns A promise that resolves to the access token. */ export async function authenticateToGoogleAdsManager(clientId: string, clientSecret: string, refreshToken: string): Promise<string> { const url = 'https://www.googleapis.com/oauth2/v3/token' const body = { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, grant_type: 'refresh_token' } const response = await fetch(url, { method: 'POST', body: JSON.stringify(body) }) const data = await response.json() return data.access_token } /** * Sends a click conversion to Google Ads Manager. * * @param customerId - The ID of the customer. * @param clickConversion - The click conversion data. * @param accessToken - The access token for authentication. * @param developerToken - The developer token for authentication. * @param options - Optional API options. * @returns A promise that resolves to void. */ export async function sendClickConversionToGoogleAdsManager(customerId: number, clickConversion: ClickConversion, accessToken: string, developerToken: string, options?: ApiOptions): Promise<void> { const url = `https://googleads.googleapis.com/v15/customers/${customerId}:uploadClickConversions` if (!options) { options = { partialFailure: false, validateOnly: false, debugEnabled: false, jobId: 0 } } const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, 'developer-token': developerToken }, body: JSON.stringify({ conversions: [clickConversion], partialFailure: options.partialFailure, validateOnly: options.validateOnly, debugEnabled: options.debugEnabled, jobId: options.jobId }) }) const data = await response.json() return data }