如何通过服务账户、REST API与TypeScript认证并发送Google Ads点击转化
Google Ads服务账户认证问题(REST接口)
我未使用官方TypeScript/JavaScript客户端库,自行参照Google Ads的REST认证文档与点击转化上传接口,尝试实现服务账户认证并向Google Ads发送点击转化数据,但目前卡在认证环节。需先解决认证问题,若后续发送转化仍有问题也需一并处理。
当前遇到的错误截图:
我的配置情况
- 拥有带开发者令牌的Google Ads账户,该令牌将在发送点击转化时使用,与服务账户认证无关。
- 已在Google Cloud项目中创建服务账户,并启用了Google Ads API:

- 已将服务账户的client ID添加到工作区的全域授权,权限范围为https://www.googleapis.com/auth/adwords:

- 上述操作在「APIs & Services」中生成了对应的「OAuth 2.0 Client IDs」,且client ID匹配:

- 额外配置了测试状态的外部OAuth同意屏幕,权限范围同样为https://www.googleapis.com/auth/adwords,但我认为服务账户认证无需此配置。
- 服务账户本身无其他相关权限,文档未提及需要额外权限,我认为添加全域授权已足够,但不确定是否有误。
我的猜测
问题可能出在两个方面:
- 权限不足;
- 对
authenticateToGoogleAdsManager函数中的refreshToken理解错误。我生成了签名JWT,但通过await fetch('https://oauth2.googleapis.com/token')仅能获取access token,不确定是否应将JWT作为refreshToken传入。
测试代码与函数实现
测试代码
// 生成签名JWT const jwt = generateJsonWebTokenForServiceAccount( serviceAccount, ['https://www.googleapis.com/auth/adwords'], 'googleads' ) // 使用JWT向Google Ads Manager认证 const authenticationResult = await authenticateToGoogleAdsManager( serviceAccount.client_id, serviceAccount.private_key, jwt ) console.log(authenticationResult) // 使用access token发送点击转化数据 const test = await sendClickConversionToGoogleAdsManager( CUSTOMERID, clickConversion, accessToken.access_token, 'DEV-TOKEN' )
函数实现
/** * 为服务账户生成JSON Web Token (JWT) * * @param serviceAccount - 包含客户端邮箱和私钥的服务账户对象 * @param scopes - 令牌授权的权限范围数组 * @param serviceName - 令牌授权的服务名称,默认值为'oauth2' * @param expirationTimeInSeconds - 令牌过期时间(秒),默认3600秒(1小时) * @returns 生成的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) } /** * 使用提供的凭证向Google Ads Manager认证 * @param clientId 认证用的客户端ID * @param clientSecret 认证用的客户端密钥 * @param refreshToken 认证用的刷新令牌 * @returns 解析为access token的Promise */ 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 } /** * 向Google Ads Manager发送点击转化数据 * * @param customerId 客户ID * @param clickConversion 点击转化数据 * @param accessToken 认证用的access token * @param developerToken 认证用的开发者令牌 * @param options 可选的API配置项 * @returns 解析为响应数据的Promise */ export async function sendClickConversionToGoogleAdsManager( customerId: number, clickConversion: ClickConversion, accessToken: string, developerToken: string, options?: ApiOptions ): Promise<any> { 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 }
内容的提问来源于stack exchange,提问作者Björn Ammon
相关产品推荐
相关产品推荐

