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.
My error:
error: invalid_client
error_description: Unauthorized
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

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.)
My code to understand what i do (I suggest to check my Question on Stackoverflow to read the codeblock)
```
// 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
)
// 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 the 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
}

![]() |
Google Ads API Team |