How do I authenticate to send a clickConversion to google Ads with a service account and over REST API with typescript

163 views
Skip to first unread message

Bjoern Ammon

unread,
Jan 31, 2024, 4:20:35 AM1/31/24
to Google Ads API and AdWords API Forum

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.Ii3VN.png

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
W0d6l.pngpB83U.png

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 }


Bjoern Ammon

unread,
Jan 31, 2024, 4:47:11 AM1/31/24
to Google Ads API and AdWords API Forum
Addition: 
The error i receive comes with authentication to googleapis
IIF1Y.png

Google Ads API Forum Advisor

unread,
Jan 31, 2024, 7:28:03 AM1/31/24
to bjo...@markbecker.one, adwor...@googlegroups.com
Hi,

Thank you for contacting the Google Ads API support team.

By reviewing your concern, I understand that you are encountering the 'invalid_client' error. I would suggest that you recreate the client Id and client secret and generate a refresh token using the new client Id and client secret key. Kindly refer to the web app flow instructions in the OAuth2.0 guide for complete instructions. Please go through the document, to know about the available client libraries.

If you still need further assistance, I would recommend that you reach out to the Google Could Platform team as they are better equipped to address your concern. 
 
This message is in relation to case "ref:!00D1U01174p.!5004Q02ryh3a:ref"

Thanks,
 
Google Logo Google Ads API Team


Bjoern Ammon

unread,
Jan 31, 2024, 7:28:35 AM1/31/24
to Google Ads API and AdWords API Forum
This error appears in python
RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.', {'error': 'unauthorized_client', 'error_description': 'Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.'})
Reply all
Reply to author
Forward
0 new messages