8000 Google Ads destination · Issue #27712 · PostHog/posthog · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Google Ads destination #27712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
17 of 21 tasks
MarconLP opened this issue Jan 21, 2025 · 52 comments · Fixed by #26088
Open
17 of 21 tasks

Google Ads destination #27712

MarconLP opened this issue Jan 21, 2025 · 52 comments · Fixed by #26088
Labels
enhancement New feature or request

Comments

@MarconLP
Copy link
Member
MarconLP commented Jan 21, 2025

Feature request

We've merged our Google Ads destination and are doing some additional testing.

TODOs:

  • Add integration logic for the customerId and actionId
  • fix default timestamp field
  • surface response errors
  • add ADWORDS_DEVELOPER_TOKEN env variable (slack)
  • re-fetch conversion actions after switching customer_id
  • switch to using separate env variables: GOOGLE_ADS_APP_CLIENT_ID & GOOGLE_ADS_APP_CLIENT_SECRET
  • add those env variables (slack)
  • show the name and ID for customerId and conversionAction
  • rename developer token var to GOOGLE_ADS_DEVELOPER_TOKEN
  • make sure the developer token isn't exposed when mocking the fetch functions
  • apply for a standard access developer token
  • cache accessibleAccounts endpoint
  • get the oauth app verified
  • be confident that the destination works as expected
  • Give some people early access to the destination, while waiting for the oauth verification
  • feat(cdp): support nested google ad accounts #28531
  • release the app to the public

Roadmap items:

cc: @benjackwhite

related: PostHog/posthog.com#10396

@MarconLP MarconLP added the enhancement New feature or request label Jan 21, 2025
@MarconLP MarconLP linked a pull request Jan 21, 2025 that will close this 8000 issue
13 tasks
@MarconLP
Copy link
Member Author

You can now use the Google Ads destination using this link: https://us.posthog.com/pipeline/new/destination/hog-template-google-ads

Please share feedback or issues you encounter.

@slimy-pufflefish
Copy link

Hi @MarconLP . I am trying to use this feature. When linking my Google Ads account, I get an error claiming that I'm missing an OAuth scope. (I can't find the exact error message anymore).

While the integration still succeeds, when trying to select a customer id, etc. I get a bunch of 500 errors.

@MarconLP
Copy link
Member Author

Hey @slimy-pufflefish, I wasn't able to replicate the issue. Are you re-using the old google ad destination? Could you share your projectId? Did that error disappear after re-linking the google ads account?

@slimy-pufflefish
Copy link
slimy-pufflefish commented Jan 27, 2025

I am clicking "Choose Google Ads Connection" -> "Connect to Google Ads". I did not have a Google Ads integration prior to today. I tried deleting my integration and re-adding it. That did not help.

My Posthog project URL is: https://us.posthog.com/project/110483/.

After doing the connection -- it says the integration was successful, but then gives

Load google ads accessible accounts failed: A server error occurred.

@NicolaiSchmid
Copy link
NicolaiSchmid commented Jan 28, 2025

same here in eu posthog (https://eu.posthog.com/project/13059):

Image

@MarconLP
Copy link
Member Author

Hey, it seems like the API request is failing with the following error The customer account can't be accessed because it is not yet enabled or has been deactivated. Can you double-check if there are any pending setup steps for your Google Ads account? Did you create the conversion goals as documented here? Let me know if that helped.

< 8000 span data-view-component="true">

@NicolaiSchmid
Copy link

I did now:

Image

but the error is still there. Do you have to let some time pass before it's accessible?
i also have a disabled ad account, maybe that's the issue?

Image

@MarconLP
Copy link
Member Author
MarconLP commented Jan 29, 2025

i also have a disabled ad account, maybe that's the issue?

That was the issue. We'll now ignore all accounts that are canceled or haven't been fully setup. The fix should be live in around 30 minutes.

@NicolaiSchmid
Copy link

wuuuuu, thank you so much!

@leng-yue
Copy link
leng-yue commented Feb 8, 2025

We have multiple accounts, and the pipeline raises The click ID or call is associated with an Ads account you don\'t have access to. Make sure you import conversions for accounts managed by your manager account. while both Customer ID and Conversion action are correct.

@davidtopf
Copy link

Hi guys, I am trying to set this up but am running into the following issue. When targetting an existing conversion action I am getting:
Error('Error from googleads.googleapis.com (status 200): The conversion action specified in the upload request isn't set up for uploading conversions., at conversions[0].conversion_action')

I don't know how to create a conversion action from scratch to target from posthog, as it remains inactive in google ads until I add a Data Source.

Any help would be greatly appreciated :)

@MarconLP
Copy link
Member Author
MarconLP commented Feb 11, 2025

We have multiple accounts, and the pipeline raises The click ID or call is associated with an Ads account you don\'t have access to. Make sure you import conversions for accounts managed by your manager account. while both Customer ID and Conversion action are correct.

Hey @leng-yue, it seems like you are trying to import a gclid that originated from a campaign in another account. Note that you'll need to create the conversion goals in the same account where the campaigns are located.

@MarconLP
Copy link
Member Author
MarconLP commented 8000 Feb 11, 2025

Hi guys, I am trying to set this up but am running into the following issue. When targetting an existing conversion action I am getting: Error('Error from googleads.googleapis.com (status 200): The conversion action specified in the upload request isn't set up for uploading conversions., at conversions[0].conversion_action')

I don't know how to create a conversion action from scratch to target from posthog, as it remains inactive in google ads until I add a Data Source.

Any help would be greatly appreciated :)

Hey @davidtopf, you'll need to create conversion goals as specified in this guide and setup the destination inside of PostHog. Note that it might take up to several days for Google to update the status to from Inactive to Active.

@davidtopf
Copy link

@MarconLP thank you so much for the quick reply! The guide is a little outdated, in that the flow described in it no longer exists, at least in my google ads account.

However, I think the gist of it is the same and if I get you correctly it's time that will get a conversion action to switch from inactive to active.

I have added a conversion action called ""New Subscription." - Posthog Event" with an offline data source marked as "Event to be set up later" and am now just going to wait a couple of date for the status to change (see pictures) rather than clicking on "Set up import" in the Actions column.

Image

Image

@MarconLP
Copy link
Member Author

@MarconLP thank you so much for the quick reply! The guide is a little outdated, in that the flow described in it no longer exists, at least in my google ads account.

Thank you for the heads up! Will get that updated ASAP.

However, I think the gist of it is the same and if I get you correctly it's time that will get a conversion action to switch from inactive to active.

I have added a conversion action called ""New Subscription." - Posthog Event" with an offline data source marked as "Event to be set up later" and am now just going to wait a couple of date for the status to change (see pictures) rather than clicking on "Set up import" in the Actions column.

Correct, assuming the Google ads destination isn't throwing any errors.

@rpv-signcom
Copy link

Should I always set-up a filter for 'initial gclid' is set when setting up a new destination for an event? Or is this smth I can skip and not worry about bcs if the person's gclid isn't set anyway then the event is not passed as a conversion?

Image

@MarconLP
Copy link
Member Author

Should I always set-up a filter for 'initial gclid' is set when setting up a new destination for an event? Or is this smth I can skip and not worry about bcs if the person's gclid isn't set anyway then the event is not passed as a conversion?

Image

Correct - You do not need to set up filters, as we skip events without a valid gclid.

if (empty(inputs.gclid)) {
    print('Empty `gclid`. Skipping...')
    return
}

@davidtopf
Copy link
davidtopf commented Feb 13, 2025

Hi @MarconLP,

I set this up and it works when I run a test, but I get this error:
Error executing function on event 0194fef5-88bb-7fe6-a692-0d04df730a0a: Error('Error from googleads.googleapis.com (status 401): {'error': {'code': 401, 'message': 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\', 'status': 'UNAUTHENTICATED'}}')

Here you can see it working:

Image

@bernhardklug
Copy link
Image

I always get this error and the account is not logged in.

@MarconLP
Copy link
Member Author

Hey, we've had an issue migrating our environment variables to a new system, which caused issues with some of our OAuth integrations. The issue should have been resolved by now. Let me know if you are still experiencing issues.

@davidtopf
Copy link

@MarconLP I am still experiencing this issue and can not send a single request:

Error executing function on event 01951508-4ab5-7a7d-bee8-e17f90de28e3: Error('Error from googleads.googleapis.com (status 401): {\'error\': {\'code\': 401, \'message\': \'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\', \'status\': \'UNAUTHENTICATED\'}}') --

@ThibeauMaerevoet
Copy link

Facing the same issue above

@pl
Copy link
Contributor
pl commented Feb 21, 2025

@davidtopf @ThibeauMaerevoet are you still seeing the issue? We had a bug but we fixed it earlier this week so the invalid authentication credentials should have gone away by now.

@ThibeauMaerevoet
Copy link
ThibeauMaerevoet commented Feb 21, 2025 via email

@davidtopf
Copy link

@pl I have not been experiencing the issue anymore! I've not had issues since sometime on the 18th I think. All calls on the 19th went through and some on 18th failed

@pl
Copy link
Contributor
pl commented Feb 24, 2025

Great, thanks for confirming.

@ThibeauMaerevoet feel free to reenable the destination as the bug is fixed.

@ThibeauMaerevoet
Copy link
ThibeauMaerevoet commented Feb 24, 2025 via email

@lksrpp
Copy link
lksrpp commented Mar 7, 2025

Hi @pl & @MarconLP
I am also using this destination now and wanted to share my feedback. In general I really like the integration and it actually helps us to consolidate some tooling within Posthog, which is great!

Two things I noticed:

Outdated Docs

  • I think this was posted already, the current docs guide you through the API-token process, which I actually did, to realise later that it isn't required anymore.
  • As this is currently a hidden destination I can totally understand why it's not yet updated, just wanted to let you know so that you have a chance to updated it before you enable this destination again

Error

  • Yesterday, I updated the destination to a new template
  • This seems to have broken the integration; the log shows the same error now for every conversion event

Error executing function on event 01956d86-ed16-72d4-bf7c-d95c501812a2: Error('Error from googleads.googleapis.com (status 400): {'error': {'code': 400, 'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT', 'details': [{'@type': 'type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure', 'errors': [{'errorCode': {'headerError': 'INVALID_LOGIN_CUSTOMER_ID'}, 'message': 'The login customer id header \'Optional[null]\' could not be validated.'}], 'requestId': '3PJ6___HaE9aCfVsQYoYjw'}]}}')

The invalid argument looks like this in the source code:

let res := fetch(f'https://googleads.googleapis.com/v18/customers/{splitByString('/', inputs.customerId)[1]}:uploadClickConversions', {
    'method': 'POST',
    'headers': {
        'Authorization': f'Bearer {inputs.oauth.access_token}',
        'Content-Type': 'application/json',
        'login-customer-id': splitByString('/', inputs.customerId)[2]

It looks like my input.customerId is x...x/x...x - could it be the case that splitByString is using an array indexing that starts at 0?

@MarconLP
Copy link
Member Author

Hi @pl & @MarconLP I am also using this destination now and wanted to share my feedback. In general I really like the integration and it actually helps us to consolidate some tooling within Posthog, which is great!

Two things I noticed:

Outdated Docs

  • I think this was posted already, the current docs guide you through the API-token process, which I actually did, to realise later that it isn't required anymore.
  • As this is currently a hidden destination I can totally understand why it's not yet updated, just wanted to let you know so that you have a chance to updated it before you enable this destination again

Error

  • Yesterday, I updated the destination to a new template
  • This seems to have broken the integration; the log shows the same error now for every conversion event

Error executing function on event 01956d86-ed16-72d4-bf7c-d95c501812a2: Error('Error from googleads.googleapis.com (status 400): {'error': {'code': 400, 'message': 'Request contains an invalid argument.', 'status': 'INVALID_ARGUMENT', 'details': [{'@type': 'type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure', 'errors': [{'errorCode': {'headerError': 'INVALID_LOGIN_CUSTOMER_ID'}, 'message': 'The login customer id header 'Optional[null]' could not be validated.'}], 'requestId': '3PJ6___HaE9aCfVsQYoYjw'}]}}')

The invalid argument looks like this in the source code:

let res := fetch(f'https://googleads.googleapis.com/v18/customers/{splitByString('/', inputs.customerId)[1]}:uploadClickConversions', {
    'method': 'POST',
    'headers': {
        'Authorization': f'Bearer {inputs.oauth.access_token}',
        'Content-Type': 'application/json',
        'login-customer-id': splitByString('/', inputs.customerId)[2]

It looks like my input.customerId is x...x/x...x - could it be the case that splitByString is using an array indexing that starts at 0?

F438

Hey @lksrpp, thank you for the feedback. I wasn't able to replicate the issue. Could you share your project/environment ID?

@posthog-contributions-bot
Copy link
Contributor

This issue has 2156 words at 29 comments. Issues this long are hard to read or contribute to, and tend to take very long to reach a conclusion. Instead, why not:

  1. Write some code and submit a pull request! Code wins arguments
  2. Have a sync meeting to reach a conclusion
  3. Create a Request for Comments and submit a PR with it to the meta repo or product internal repo

Is this issue intended to be sprawling? Consider adding label epic or sprint to indicate this.

@lksrpp
Copy link
lksrpp commented Mar 10, 2025

@MarconLP, sure.

Session: https://us.posthog.com/project/sTMFPsFhdP1Ssg/replay/01957fde-c376-710a-86e1-cbf61a9206cf?t=0
Admin: http://go/adminOrgEU/018bfb3f-1be1-0000-eaa4-606a4fc275f5 (project ID 13779)
Sentry: http://go/sentryEU/13779

Reproduction Steps:

  • I saw that the template had an update available
  • Image
  • I updated my template; in the source code segment, the login-custom-id is defined as on the screenshot
  • Image
  • This throws the error as described earlier
  • Updating this snippet to 'login-customer-id': splitByString('/', inputs.customerId)[1] solves the issue in my case

@MarconLP
Copy link
Member Author

@MarconLP, sure.

Session: https://us.posthog.com/project/sTMFPsFhdP1Ssg/replay/01957fde-c376-710a-86e1-cbf61a9206cf?t=0
Admin: http://go/adminOrgEU/018bfb3f-1be1-0000-eaa4-606a4fc275f5 (project ID 13779)
Sentry: http://go/sentryEU/13779

Reproduction Steps:

  • I saw that the template had an update available
  • Image
  • I updated my template; in the source code segment, the login-custom-id is defined as on the screenshot
  • Image
  • This throws the error as described earlier
  • Updating this snippet to 'login-customer-id': splitByString('/', inputs.customerId)[1] solves the issue in my case

Hey @lksrpp, it looks like you managed to update the template but weren't required to re-select the customerId field with the new format. I can see in the history tab, that you've since (3 days ago) updated the customerId field as well, so the workaround isn't required anymore.

It looks like you have another error:

The click ID or call is associated with an Ads account you don\'t have access to. Make sure you import conversions for accounts managed by your manager account.

Can you confirm that the conversion goal is located within the same account that is running the campaign?

@lksrpp
Copy link
lksrpp commented Mar 10, 2025

Hey @MarconLP ,

thanks for your feedback!

I can see in the history tab, that you've since (3 days ago) updated the customerId field as well, so the workaround isn't required anymore.

I'm not sure if I understand this correctly, sorry for the confusion. If I create a new destination from the updated template, and I select my Google Ads Account in the customer id field, it is very briefly shown as "0123456789/0123456789" before it resolves to its actual name. Wouldn't this still throw an error for 'login-customer-id': splitByString('/', inputs.customerId)[2] as it did before? Maybe I missed if I have to do something else in the account selection.

It looks like you have another error:

The click ID or call is associated with an Ads account you don't have access to. Make sure you import conversions for accounts managed by your manager account.

Can you confirm that the conversion goal is located within the same account that is running the campaign?

Yes. In this case, we have secondary Google Ads account that was sending users in as well; these conversion events were throwing the error. I updated the matching event to exclude the utm_campaign from the other Ads Account. The primary account works correctly with the configured destination. I can see the correct conversions in the Google Ads UI.

@MarconLP
Copy link
Member Author

I'm not sure if I understand this correctly, sorry for the confusion. If I create a new destination from the updated template, and I select my Google Ads Account in the customer id field, it is very briefly shown as "0123456789/0123456789" before it resolves to its actual name. Wouldn't this still throw an error for 'login-customer-id': splitByString('/', inputs.customerId)[2] as it did before? Maybe I missed if I have to do something else in the account selection.

No, our arrays are 1-indexed. So splitByString('/', inputs.customerId)[2] will select the second part of the customerId. In our previous version the customerId was a single ID like 123456789 which caused the error.

@jokull
Copy link
jokull commented Mar 11, 2025

Might want to add a note to your docs:

Heads up - it might take up to 6 hours for freshly created Goals (conversion actions) to become available via the Gooele Ads API integration.

@MarconLP
Copy link
Member Author

Might want to add a note to your docs:

Heads up - it might take up to 6 hours for freshly created Goals (conversion actions) to become available via the Gooele Ads API integration.

Good point! Added details around google's processing times.

@stepnov
Copy link
stepnov commented Apr 18, 2025

We have integrated conversion actions, but in the logs we see this message:

Error executing function on event 01964926-e23a-7d7c-ba96-2f6cb35f50f8: Error('Error from googleads.googleapis.com (status 200): Can\'t import events to a conversion action that was just created. Try importing again in 6 hours., at conversions[0].conversion_action')

Should we do something, or will Posthog automatically push this event again to Google Ads?

@MarconLP
Copy link
Member Author

We have integrated conversion actions, but in the logs we see this message:

Error executing function on event 01964926-e23a-7d7c-ba96-2f6cb35f50f8: Error('Error from googleads.googleapis.com (status 200): Can\'t import events to a conversion action that was just created. Try importing again in 6 hours., at conversions[0].conversion_action')

Should we do something, or will Posthog automatically push this event again to Google Ads?

We won't automatically retry failed requests, but you can wait 6 hours and use the testing tab to retry several events at once.

@bernhardklug
Copy link

I do not know why but it says that the conversions are working but they are not visible in Google Ads even after a day.

Image Image

Any known problem?

@martindavis
Copy link
martindavis commented May 2, 2025

I do not know why but it says that the conversions are working but they are not visible in Google Ads even after a day.

Image Image
Any known problem?

Have you checked to see if you're getting an error message about the gclid missing in the Logs Tab?
Image

One thing I'm running into is that the gclid does not get set on the user automatically. Additionally, I believe the Google Ads Data Pipeline plugin is pulling event data from the DB slightly differently than the standard Activities. If I view an event in the activities tab, I can see the gclid associated with the event. However, if I view it in the Google Ads Test Tab, I do not see the gclid.

I've opened a bug to make sure I'm not missing something. The only workaround I can think of for now is creating a dummy event so I can use the set_once functionality to store the user's gclid on every page view.

@bernhardklug
Copy link
bernhardklug commented May 7, 2025
Image Image Image

It says it works but 0 conversions are sent.

@MarconLP
Copy link
Member Author
MarconLP commented May 7, 2025

Image Image Image
It says it works but 0 conversions are sent.

Hey @bernhardklug, can you check the logs of those invocations? An event that has been filtered out because the gclid is missing will be considered successful.

Image

@jokull
Copy link
jokull commented May 7, 2025

Just had Google error during ingestion because the distinctId of the event is flagged as PII. Anyone else have this? If I comment out the orderId it works.

@MarconLP
Copy link
Member Author
MarconLP commented May 7, 2025

Just had Google error during ingestion because the distinctId of the event is flagged as PII. Anyone else have this? If I comment out the orderId it works.

Could you share a screenshot of the error?

@jokull
Copy link
jokull commented May 7, 2025
Image

Code (order_id should be uncommented)

if (empty(inputs.gclid)) {
    print('Empty `gclid`. Skipping...')
    return
}

let body := {
    'conversions': [
        {
            'gclid': inputs.gclid,
            'conversion_action': f'customers/{replaceAll(inputs.customerId, '-', '')}/conversionActions/{inputs.conversionActionId}',
            'conversion_date_time': inputs.conversionDateTime,
            // 'order_id': inputs.orderId,
            'user_identifiers': [{'hashed_email': inputs.hashedEmail}]
        }
    ],
    'partialFailure': true
}

if (not empty(inputs.conversionValue)) {
    body.conversions[1].conversion_value := inputs.conversionValue
}
if (not empty(inputs.currencyCode)) {
    body.conversions[1].currency_code := inputs.currencyCode
}

let res := fetch(f'https://googleads.googleapis.com/v18/customers/{replaceAll(inputs.customerId, '-', '')}:uploadClickConversions', {
    'method': 'POST',
    'headers': {
        'Authorization': f'Bearer {inputs.oauth.access_token}',
        'Content-Type': 'application/json'
    },
    'body': body
})

if (res.status >= 400) {
    throw Error(f'Error from googleads.googleapis.com (status {res.status}): {res.body}')
} else if (not empty(res.body.partialFailureError)) {
    throw Error(f'Error from googleads.googleapis.com (status {res.status}): {res.body.partialFailureError.message}')
}

Settings:

Image

@MarconLP
Copy link
Member Author
MarconLP commented May 8, 2025
Image Code (order_id should be uncommented)
if (empty(inputs.gclid)) {
    print('Empty `gclid`. Skipping...')
    return
}

let body := {
    'conversions': [
        {
            'gclid': inputs.gclid,
            'conversion_action': f'customers/{replaceAll(inputs.customerId, '-', '')}/conversionActions/{inputs.conversionActionId}',
            'conversion_date_time': inputs.conversionDateTime,
            // 'order_id': inputs.orderId,
            'user_identifiers': [{'hashed_email': inputs.hashedEmail}]
        }
    ],
    'partialFailure': true
}

if (not empty(inputs.conversionValue)) {
    body.conversions[1].conversion_value := inputs.conversionValue
}
if (not empty(inputs.currencyCode)) {
    body.conversions[1].currency_code := inputs.currencyCode
}

let res := fetch(f'https://googleads.googleapis.com/v18/customers/{replaceAll(inputs.customerId, '-', '')}:uploadClickConversions', {
    'method': 'POST',
    'headers': {
        'Authorization': f'Bearer {inputs.oauth.access_token}',
        'Content-Type': 'application/json'
    },
    'body': body
})

if (res.status >= 400) {
    throw Error(f'Error from googleads.googleapis.com (status {res.status}): {res.body}')
} else if (not empty(res.body.partialFailureError)) {
    throw Error(f'Error from googleads.googleapis.com (status {res.status}): {res.body.partialFailureError.message}')
}

Settings:

Image

Hey, thank you for sharing those details. Looks like you are using an email as the distinct_id, so you'll need to hash it before using it as the order_id. You can do this using the following expression: {sha256Hex(event.distinct_id)}.

I would recommend that you switch over to using {person.id} instead, because a single user could have multiple distinct_ids attached. Check out this page for more details.

@jokull
Copy link
jokull commented May 8, 2025

Oh I feel stupid now. Thank you.

@bernhardklug
Copy link

Image Image Image
It says it works but 0 conversions are sent.

Hey @bernhardklug, can you check the logs of those invocations? An event that has been filtered out because the gclid is missing will be considered successful.

Image

The weird thing is, in Google, it only shows three conversions – even though it said five were successful. One of them had the Google Click ID, but somehow the others didn’t. I don’t really understand why, but let’s count that one as valid. Still, a few conversions are missing, and I’m not sure why.

Image Image

I’ve now set up a second version to test, and the new one seems to work better. But the old setup… in the last 30 days, it only recorded a few conversions, even though Posthog shows many more as successful. Still trying to figure out what’s going wrong there.

Image Image Image

@meikelmosby
Copy link
Contributor

@MarconLP A user raised a concern about the timestamps

Google ads template has invalid datetimes because it does not convert timezone

Steps:

  1. Use posthog CDP to setup google ads as a destination
  2. Use the built in template for google ads
  3. Send events with a gclid for conversion tracking

Expected:
Events get sent

Actual:
Get errors from google that the event timestamp is before the click

The problem is the template simply removes the timezone from the event, instead of converting timezones.

The solution is to pass the UTC timezone into formatDateTime
e.g.
{formatDateTime(toDateTime(event.timestamp), '%Y-%m-%d %H:%i:%S', 'UTC')}+00:00

I'm growing concerned that the cdp code isn't production ready.

"default": "{formatDateTime(toDateTime(event.timestamp), '%Y-%m-%d %H:%i:%S')}+00:00",

c.f. https://posthoghelp.zendesk.com/agent/tickets/30985

@MarconLP
Copy link
Member Author
MarconLP commented May 19, 2025

@MarconLP A user raised a concern about the timestamps

Google ads template has invalid datetimes because it does not convert timezone
Steps:

  1. Use posthog CDP to setup google ads as a destination
  2. Use the built in template for google ads
  3. Send events with a gclid for conversion tracking

Expected:
Events get sent
Actual:
Get errors from google that the event timestamp is before the click
The problem is the template simply removes the timezone from the event, instead of converting timezones.
The solution is to pass the UTC timezone into formatDateTime
e.g.
{formatDateTime(toDateTime(event.timestamp), '%Y-%m-%d %H:%i:%S', 'UTC')}+00:00
I'm growing concerned that the cdp code isn't production ready.

  [posthog/posthog/cdp/templates/google_ads/template_google_ads.py](https://github.com/PostHog/posthog/blob/e4b39aa33fa055e7138a47146b82cd9595ac040f/posthog/cdp/templates/google_ads/template_google_ads.py#L102)


     Line 102
  in
  [e4b39aa](/PostHog/posthog/commit/e4b39aa33fa055e7138a47146b82cd9595ac040f)





    
      
       "default": "{formatDateTime(toDateTime(event.timestamp), '%Y-%m-%d %H:%i:%S')}+00:00",

c.f. posthoghelp.zendesk.com/agent/tickets/30985

Hey - we hardcoded the timezone to +00:00 in that input expression because the event.timestamp has already been converted to the UTC timezone.

If I capture an event at 10:30 in the UTC+2 timestamp. The conversion date will evaluate to 2025-05-19 08:30:29+00:00, which is what we would expect. If you see an incorrect timestamp, could you share the ID of that event?

We've managed to reduce the error rate for invalid timestamps by adding one or two days to the timestamp as recommended by Google (source)

Invalid conversion times
The offline conversion can't happen before the ad click. Add 1-2 days to your conversion time in your upload, or check that the time zone is properly set.

You can add or remove days to the timestamp by using the following input expression:

{formatDateTime(toDateTime(toUnixTimestamp(event.timestamp) - 2*24*60*60), '%Y-%m-%d %H:%i:%S+00:00')}

@richardscottclark
Copy link

@bernhardklug - we have the same issue, and the conversions stopped exactly on May 1st, and we cannot get them working again.

We get this response from Google Ads Conv API, which LOOKS like a success, but the conversion never appears.

{"status":200,"body":{"results":[{"gclid":"EAIaIQobChMIk5m50-_XjQMVeyvUAR2QehVxEAAYASAAEgLiXXXXXX","conversionAction":"customers/2490937362/conversionActions/7084518XXX","conversionDateTime":"2025-06-05 10:03:54+00:00"}],"jobId":"8379392534202799735"}}

We tried temporarily disabling enhanced conversions (completely) but it made no difference.

If we delete/recreate the conversion, we'll disrupt our machine bid model - a pretty big deal for us.

I would be thrilled to hear of any fixes you found?

Or even any novel diagnostics / debugging you thought up?

@richardscottclark
Copy link
richardscottclark commented Jun 16, 2025

Note: I discovered that Google Ads API v16 was sunsetted on May 1st - exatctly when we lost the connection.
I've not made any cause-effect connections yet, and I realize the source are using v18, but am wondering if something else I cannot see in the source.

Google API team contact tells me the logged API response above was insufficient and incomplete, so cannot help me.

I did submit a ticket to Posthog support out of desperation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

0