Skip to content

Google Ads integration#4148

Open
devkiran wants to merge 16 commits into
mainfrom
google-ads
Open

Google Ads integration#4148
devkiran wants to merge 16 commits into
mainfrom
google-ads

Conversation

@devkiran

@devkiran devkiran commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added Google Ads integration with OAuth setup and account selection.
    • Added configuration for lead and sale conversion actions.
    • Automatically sends tracked leads and sales as Google Ads conversions.
    • Added support for managing and uninstalling the integration from settings.
  • Access
    • Google Ads is available on advanced and enterprise plans.
  • Maintenance
    • Improved consistency of error details in background operation logs.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Jul 15, 2026 12:34pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Google Ads integration support is added across OAuth installation, customer and conversion-action configuration, Google Ads API access, lead and sale conversion uploads, installed-workspace tracking, and dashboard wiring. Error metadata logging is centralized, and webhook receiver identification is updated.

Google Ads Integration

Layer / File(s) Summary
Configuration and data contracts
apps/web/.env.example, packages/utils/src/constants/integrations.ts, apps/web/lib/integrations/google-ads/*
Adds Google Ads environment variables, identifiers, API settings, workspace allowlisting, and Zod schemas for credentials, customers, settings, conversion actions, and uploads.
OAuth installation and persistence
apps/web/lib/integrations/google-ads/oauth.ts, apps/web/app/(ee)/api/google-ads/callback/route.ts, apps/web/lib/integrations/install.ts, apps/web/lib/actions/get-integration-install-url.ts, apps/web/scripts/create-integration.ts
Adds authorization URL generation, token refresh and locking, callback validation, encrypted credential persistence, workspace eligibility checks, and Google Ads integration seeding.
Google Ads API access
apps/web/lib/integrations/google-ads/api.ts
Adds authenticated Google Ads and Data Manager requests, customer discovery, conversion-action listing, click-conversion uploads, and login-customer inference.
Settings selection and validation
apps/web/lib/integrations/google-ads/ui/settings.tsx, apps/web/lib/integrations/google-ads/update-google-ads-settings.ts, apps/web/app/(ee)/api/google-ads/conversion-actions/route.ts, apps/web/app/app.dub.co/.../page-client.tsx
Adds customer and conversion-action selection, server-side validation and persistence, conversion-action loading, and Google Ads settings-page integration.
Conversion upload pipeline
apps/web/lib/api/conversions/track-lead.ts, apps/web/lib/api/conversions/track-sale.ts, apps/web/lib/integrations/google-ads/upload-conversion.ts, apps/web/app/(ee)/api/google-ads/upload-conversion/route.ts
Queues lead and sale conversions through QStash, extracts Google click identifiers, validates payloads, refreshes OAuth credentials, retries uploads, and calls the Google Ads API.
Installed-workspace tracking and lifecycle wiring
apps/web/lib/integrations/google-ads/installed-workspaces.ts, apps/web/app/(ee)/api/cron/google-ads/*, apps/web/vercel.json, apps/web/app/api/integrations/uninstall/route.ts
Maintains the Redis installed-workspace set, synchronizes it with a scheduled cron route, and removes workspaces when Google Ads is uninstalled.

Error metadata logging

Layer / File(s) Summary
Standardized asynchronous error logging
apps/web/lib/axiom/server.ts, apps/web/lib/api/rewards/queue-reward-processing.ts, apps/web/lib/cron/qstash-workflow.ts, apps/web/lib/partner-referrals/attribute-referring-partner.ts, apps/web/lib/upstash/redis-streams/workspace-click-events.ts
Adds getErrorMetadata and uses it for structured error logging across asynchronous QStash, workflow, referral, and click-event failures.

Webhook utility update

Layer / File(s) Summary
Webhook receiver identification
apps/web/lib/webhook/utils.ts
Replaces link-click trigger inspection with hostname-based webhook receiver identification and a "user" fallback.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

OAuth Install Flow

sequenceDiagram
  participant User
  participant InstallAction
  participant GoogleAdsOAuthProvider
  participant CallbackRoute
  participant Prisma
  User->>InstallAction: request Google Ads install URL
  InstallAction->>GoogleAdsOAuthProvider: generateAuthUrl(workspace.id)
  GoogleAdsOAuthProvider-->>User: return authorization URL
  User->>CallbackRoute: OAuth callback with code
  CallbackRoute->>GoogleAdsOAuthProvider: exchange code for tokens
  CallbackRoute->>Prisma: persist credentials and settings
  CallbackRoute-->>User: redirect to settings
Loading

Conversion Upload Flow

sequenceDiagram
  participant ConversionTracker
  participant QueueGoogleAdsConversionUpload
  participant QStash
  participant UploadRoute
  participant UploadGoogleAdsConversion
  participant GoogleAdsApi
  ConversionTracker->>QueueGoogleAdsConversionUpload: enqueue lead or sale conversion
  QueueGoogleAdsConversionUpload->>QStash: publish deduplicated job
  QStash->>UploadRoute: POST upload-conversion
  UploadRoute->>UploadGoogleAdsConversion: validate payload
  UploadGoogleAdsConversion->>GoogleAdsApi: upload click conversion
  GoogleAdsApi-->>UploadGoogleAdsConversion: return upload response
Loading

Possibly related PRs

  • dubinc/dub#3588: Both update integration settings UI wiring and plan-capability gating.
  • dubinc/dub#3740: Both modify lead and sale conversion tracking side effects.
  • dubinc/dub#4033: Both modify sale conversion tracking logic.

Suggested reviewers: steven-tey

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title matches the feature area, but it is too generic to convey the main change in the pull request. Use a more specific title such as 'Add Google Ads integration with OAuth, settings, and conversion uploads'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch google-ads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/web/scripts/create-integration.ts (1)

33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log message says "created" but upsert can also update.

With prisma.integration.upsert, this script may update an existing record rather than create a new one. The log message should reflect both cases for clarity during bootstrap runs.

♻️ Suggested fix
-  console.log(`${integration.name} integration created`, integration);
+  console.log(`${integration.name} integration upserted`, integration);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/scripts/create-integration.ts` at line 33, The log message in
create-integration.ts is misleading because prisma.integration.upsert can either
create or update an Integration. Update the console.log in the
create-integration script to reflect both outcomes, using the integration result
from the upsert flow and a neutral message such as “created or updated” so
bootstrap logs stay accurate.
apps/web/lib/integrations/google-ads/upload-conversion.ts (1)

125-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Leftover debug log runs on every conversion upload.

console.log("uploadClickConversion response", response) looks like a debug artifact and will emit the full API response on every successful upload. Remove it or gate it behind a debug flag / lower log level to avoid production log noise.

♻️ Proposed removal
-  console.log("uploadClickConversion response", response);
-
   const partialFailureError = (response as any)?.partialFailureError;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/integrations/google-ads/upload-conversion.ts` at line 125, The
uploadClickConversion flow in upload-conversion.ts still emits a leftover debug
console.log for every successful upload; remove this logging or guard it behind
an explicit debug flag/lower log level in uploadClickConversion so production
runs do not print the full API response.
apps/web/lib/integrations/google-ads/api.ts (1)

32-64: 🚀 Performance & Scalability | 🔵 Trivial

Every trackLead/trackSale now issues an extra installedIntegration.findFirst even for workspaces without Google Ads.

This DB lookup runs for every lead/sale (matching the TODO: How to optimize this call?). Although it's invoked inside waitUntil side-effects and doesn't block the response, it adds a per-event query across all workspaces. Consider gating it behind a cheaper signal (e.g. a cached workspace flag / Redis set of Google-Ads-enabled workspaces) so the query only runs when the integration is actually installed.

Want me to open a follow-up issue or sketch a cached-flag approach?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/integrations/google-ads/api.ts` around lines 32 - 64, The
queueGoogleAdsConversionUpload path is doing a per-event
installedIntegration.findFirst lookup for every trackLead/trackSale, even when
Google Ads is not installed. Move this check behind a cheaper prefilter, such as
a cached workspace-level flag or Redis set of Google-Ads-enabled workspaces, and
keep the existing qstash.publishJSON flow unchanged. Use
queueGoogleAdsConversionUpload, installedIntegration.findFirst, and the
GOOGLE_ADS_INTEGRATION_ID/payload.workspaceId gating logic to locate the
optimization.
apps/web/app/(ee)/api/gad/callback/route.ts (1)

32-39: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider moving findFirstOrThrow inside the try-catch for graceful error handling.

If the Google Ads integration record doesn't exist in the database (e.g., bootstrap script hasn't been run), findFirstOrThrow on line 32 throws outside the try-catch block, resulting in an unhandled 500 error. Moving it inside the try block would let the catch handler redirect the user to the login page with a user-friendly error message instead.

♻️ Suggested refactor
 export const GET = async (req: Request) => {
   const { searchParams } = new URL(req.url);

   const session = await getSession();

   if (!session?.user.id) {
     const callbackPath = `/api/gad/callback?${searchParams.toString()}`;
     redirect(`/login?next=${encodeURIComponent(callbackPath)}`);
   }

-  const integration = await prisma.integration.findFirstOrThrow({
-    where: {
-      id: GOOGLE_ADS_INTEGRATION_ID,
-    },
-    select: {
-      slug: true,
-    },
-  });
-
   let workspaceSlug: string | null = null;
   let errorMessage: string | null = null;

   try {
+    const integration = await prisma.integration.findFirstOrThrow({
+      where: {
+        id: GOOGLE_ADS_INTEGRATION_ID,
+      },
+      select: {
+        slug: true,
+      },
+    });
+
     const { token, contextId: workspaceId } =
       await googleAdsOAuthProvider.exchangeCodeForToken<string>(req);

Note: integration.slug is used on line 148, so you'd need to scope the variable accordingly (e.g., declare it outside the try or store the slug).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(ee)/api/gad/callback/route.ts around lines 32 - 39, Move the
prisma.integration.findFirstOrThrow lookup in the callback route into the
existing try-catch so missing Google Ads integration records are handled
gracefully instead of throwing an unhandled 500. Make sure the integration slug
remains available where it is later used in the route, either by declaring the
variable outside the try or extracting and storing the slug from the integration
result, and keep the catch path redirecting to login with the user-friendly
error handling already in place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/lib/api/conversions/track-sale.ts`:
- Around line 627-635: The Google Ads upload in queueGoogleAdsConversionUpload
is sending conversionValue in minor units instead of major units. Update the
track-sale conversion payload so the amount passed from amount is converted to
major currency units before queuing the upload, keeping the rest of the
workspaceId, eventType, clickId, conversionDateTime, eventId, and currencyCode
fields unchanged.

In `@apps/web/lib/integrations/google-ads/oauth.ts`:
- Around line 131-142: The Google Ads OAuth setup currently hardcodes the
redirect URI, which breaks authorization outside local development. Update the
googleAdsOAuthProvider configuration in oauth.ts so redirectUri is derived from
an environment-aware value instead of the fixed localhost callback, and ensure
it still points to the correct Google Ads callback route in each environment.

---

Nitpick comments:
In `@apps/web/app/`(ee)/api/gad/callback/route.ts:
- Around line 32-39: Move the prisma.integration.findFirstOrThrow lookup in the
callback route into the existing try-catch so missing Google Ads integration
records are handled gracefully instead of throwing an unhandled 500. Make sure
the integration slug remains available where it is later used in the route,
either by declaring the variable outside the try or extracting and storing the
slug from the integration result, and keep the catch path redirecting to login
with the user-friendly error handling already in place.

In `@apps/web/lib/integrations/google-ads/api.ts`:
- Around line 32-64: The queueGoogleAdsConversionUpload path is doing a
per-event installedIntegration.findFirst lookup for every trackLead/trackSale,
even when Google Ads is not installed. Move this check behind a cheaper
prefilter, such as a cached workspace-level flag or Redis set of
Google-Ads-enabled workspaces, and keep the existing qstash.publishJSON flow
unchanged. Use queueGoogleAdsConversionUpload, installedIntegration.findFirst,
and the GOOGLE_ADS_INTEGRATION_ID/payload.workspaceId gating logic to locate the
optimization.

In `@apps/web/lib/integrations/google-ads/upload-conversion.ts`:
- Line 125: The uploadClickConversion flow in upload-conversion.ts still emits a
leftover debug console.log for every successful upload; remove this logging or
guard it behind an explicit debug flag/lower log level in uploadClickConversion
so production runs do not print the full API response.

In `@apps/web/scripts/create-integration.ts`:
- Line 33: The log message in create-integration.ts is misleading because
prisma.integration.upsert can either create or update an Integration. Update the
console.log in the create-integration script to reflect both outcomes, using the
integration result from the upsert flow and a neutral message such as “created
or updated” so bootstrap logs stay accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54391bad-16d5-4b27-a688-315b00efc940

📥 Commits

Reviewing files that changed from the base of the PR and between 661a349 and dff2ce2.

📒 Files selected for processing (19)
  • apps/web/.env.example
  • apps/web/app/(ee)/api/gad/callback/route.ts
  • apps/web/app/(ee)/api/gad/conversion-actions/route.ts
  • apps/web/app/(ee)/api/gad/upload-conversion/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx
  • apps/web/lib/actions/get-integration-install-url.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/integrations/google-ads/api.ts
  • apps/web/lib/integrations/google-ads/constants.ts
  • apps/web/lib/integrations/google-ads/oauth.ts
  • apps/web/lib/integrations/google-ads/schema.ts
  • apps/web/lib/integrations/google-ads/ui/settings.tsx
  • apps/web/lib/integrations/google-ads/update-google-ads-settings.ts
  • apps/web/lib/integrations/google-ads/upload-conversion.ts
  • apps/web/lib/integrations/install.ts
  • apps/web/lib/webhook/utils.ts
  • apps/web/scripts/create-integration.ts
  • packages/utils/src/constants/integrations.ts

Comment thread apps/web/lib/api/conversions/track-sale.ts
Comment thread apps/web/lib/integrations/google-ads/oauth.ts
@devkiran
devkiran marked this pull request as draft July 9, 2026 10:10
@devkiran
devkiran marked this pull request as ready for review July 15, 2026 09:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/lib/integrations/google-ads/upload-conversion.ts`:
- Around line 39-61: Update extractGoogleAdsClickId to wrap getSearchParams(url)
in a try...catch, returning null when URL parsing fails and logging a warning
with the malformed URL error details. Preserve the existing gclid, gbraid, and
wbraid extraction behavior for valid URLs.
- Around line 147-155: Normalize conversionDateTime before passing it to
googleAdsApi.uploadClickConversion: parse the incoming timestamp string and
format it as Google Ads requires, yyyy-MM-dd HH:mm:ss±HH:mm, rather than
forwarding the toISOString() value with milliseconds and Z. Keep the existing
upload payload and other fields unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4e98f19-008e-404a-beeb-2c9158730676

📥 Commits

Reviewing files that changed from the base of the PR and between e6a49bf and ada9cbd.

📒 Files selected for processing (11)
  • apps/web/.env.example
  • apps/web/app/(ee)/api/gad/conversion-actions/route.ts
  • apps/web/lib/actions/get-integration-install-url.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/integrations/google-ads/api.ts
  • apps/web/lib/integrations/google-ads/constants.ts
  • apps/web/lib/integrations/google-ads/oauth.ts
  • apps/web/lib/integrations/google-ads/update-google-ads-settings.ts
  • apps/web/lib/integrations/google-ads/upload-conversion.ts
  • apps/web/lib/integrations/google-ads/utils.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/lib/integrations/google-ads/constants.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/app/(ee)/api/gad/conversion-actions/route.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/integrations/google-ads/update-google-ads-settings.ts

Comment thread apps/web/lib/integrations/google-ads/upload-conversion.ts Outdated
Comment thread apps/web/lib/integrations/google-ads/upload-conversion.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/gad/callback/route.ts (1)

134-138: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prevent leaking internal error details to the frontend.

Falling back to error.message for any generic Error can leak internal stack details to the user via the redirect URL (e.g., Prisma's "No Project found", a stringified ZodError JSON array, or a TypeError).

Restrict error.message strictly to known DubApiError instances.

🔒 Proposed fix
   } catch (error) {
     errorMessage =
-      error instanceof DubApiError || error instanceof Error
+      error instanceof DubApiError
         ? error.message
         : "Failed to connect Google Ads. Please try again.";
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(ee)/api/gad/callback/route.ts around lines 134 - 138, Update
the errorMessage assignment in the Google Ads callback error handler to use
error.message only for DubApiError instances. For generic Error values and all
other thrown values, return the existing safe fallback message instead of
exposing internal details through the redirect URL.
apps/web/lib/integrations/google-ads/update-google-ads-settings.ts (1)

69-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow loginCustomerId in conversion-action validation. The current prefix check only accepts customers/${customerId}/conversionActions/, which rejects valid cross-account conversion actions owned by the manager account. Use the provided loginCustomerId here, or accept both the selected customer and login account prefixes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/integrations/google-ads/update-google-ads-settings.ts` around
lines 69 - 99, Update the conversion-action validation in the customerId block
to accept actions prefixed by the provided loginCustomerId as well as the
selected customer ID. Reuse the normalized account IDs when constructing valid
prefixes, and preserve rejection of actions that match neither account.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/web/app/`(ee)/api/gad/callback/route.ts:
- Around line 134-138: Update the errorMessage assignment in the Google Ads
callback error handler to use error.message only for DubApiError instances. For
generic Error values and all other thrown values, return the existing safe
fallback message instead of exposing internal details through the redirect URL.

In `@apps/web/lib/integrations/google-ads/update-google-ads-settings.ts`:
- Around line 69-99: Update the conversion-action validation in the customerId
block to accept actions prefixed by the provided loginCustomerId as well as the
selected customer ID. Reuse the normalized account IDs when constructing valid
prefixes, and preserve rejection of actions that match neither account.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 017cc261-1f71-4b95-8943-1dd77f4d89ca

📥 Commits

Reviewing files that changed from the base of the PR and between ada9cbd and 3e46399.

📒 Files selected for processing (13)
  • apps/web/app/(ee)/api/gad/callback/route.ts
  • apps/web/app/(ee)/api/gad/conversion-actions/route.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/api/rewards/queue-reward-processing.ts
  • apps/web/lib/axiom/server.ts
  • apps/web/lib/cron/qstash-workflow.ts
  • apps/web/lib/integrations/google-ads/api.ts
  • apps/web/lib/integrations/google-ads/schema.ts
  • apps/web/lib/integrations/google-ads/ui/settings.tsx
  • apps/web/lib/integrations/google-ads/update-google-ads-settings.ts
  • apps/web/lib/integrations/google-ads/upload-conversion.ts
  • apps/web/lib/partner-referrals/attribute-referring-partner.ts
  • apps/web/lib/upstash/redis-streams/workspace-click-events.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/web/app/(ee)/api/gad/conversion-actions/route.ts
  • apps/web/lib/integrations/google-ads/schema.ts
  • apps/web/lib/integrations/google-ads/upload-conversion.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/integrations/google-ads/ui/settings.tsx
  • apps/web/lib/integrations/google-ads/api.ts

@devkiran

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/lib/integrations/google-ads/upload-conversion.ts (1)

163-173: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Normalize Google Ads timestamps before uploading.

The conversionDateTime string comes from new Date().toISOString(), which produces a string in the format YYYY-MM-DDTHH:mm:ss.sssZ. However, the Google Ads API strictly requires the yyyy-mm-dd hh:mm:ss+|-hh:mm format. You should format it correctly before executing the upload request.

🛡️ Proposed fix
+        // Convert ISO 8601 (e.g. 2024-05-01T12:00:00.000Z) to Google Ads format (yyyy-mm-dd hh:mm:ss+|-hh:mm)
+        const formattedConversionDateTime = conversionDateTime.includes("T")
+          ? conversionDateTime.replace("T", " ").substring(0, 19) + "+00:00"
+          : conversionDateTime;
+
         const response = await googleAdsApi.uploadClickConversion({
           customerId: settings.customerId,
           conversionAction,
           googleClickId,
-          conversionDateTime,
+          conversionDateTime: formattedConversionDateTime,
           conversionValue,
           currencyCode,
           conversionCount,
           eventId,
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/integrations/google-ads/upload-conversion.ts` around lines 163 -
173, Normalize conversionDateTime before the googleAdsApi.uploadClickConversion
call in the upload conversion flow. Convert the ISO timestamp from new
Date().toISOString() into Google Ads’ required yyyy-mm-dd hh:mm:ss+|-hh:mm
format, removing milliseconds and replacing the T separator and trailing Z with
the required space and UTC offset.
🧹 Nitpick comments (1)
apps/web/lib/integrations/google-ads/upload-conversion.ts (1)

175-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove debug logging.

Consider removing the console.log left behind from debugging, or convert it to a structured logger.info log if keeping this visibility is necessary.

♻️ Proposed fix
-        console.log("uploadClickConversion response", response);
-
         return response;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/integrations/google-ads/upload-conversion.ts` around lines 175 -
177, Remove the console.log statement in the uploadClickConversion response
handling. If visibility into the response is needed for monitoring or debugging,
replace it with a structured logger.info call instead of using console.log. The
statement to remove or replace is the console.log("uploadClickConversion
response", response) line that appears before the return statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/lib/integrations/google-ads/upload-conversion.ts`:
- Around line 18-40: Wrap the getSearchParams call and click-ID extraction logic
in extractGoogleAdsClickId with try...catch; return null from the catch when URL
parsing throws, while preserving the existing gclid, gbraid, wbraid, and
no-match behavior.

---

Outside diff comments:
In `@apps/web/lib/integrations/google-ads/upload-conversion.ts`:
- Around line 163-173: Normalize conversionDateTime before the
googleAdsApi.uploadClickConversion call in the upload conversion flow. Convert
the ISO timestamp from new Date().toISOString() into Google Ads’ required
yyyy-mm-dd hh:mm:ss+|-hh:mm format, removing milliseconds and replacing the T
separator and trailing Z with the required space and UTC offset.

---

Nitpick comments:
In `@apps/web/lib/integrations/google-ads/upload-conversion.ts`:
- Around line 175-177: Remove the console.log statement in the
uploadClickConversion response handling. If visibility into the response is
needed for monitoring or debugging, replace it with a structured logger.info
call instead of using console.log. The statement to remove or replace is the
console.log("uploadClickConversion response", response) line that appears before
the return statement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a73ee0de-e0c3-4311-8c3a-8c5573a4b399

📥 Commits

Reviewing files that changed from the base of the PR and between 3e46399 and 908b916.

📒 Files selected for processing (15)
  • apps/web/app/(ee)/api/cron/google-ads/sync-installed-workspaces/route.ts
  • apps/web/app/(ee)/api/google-ads/callback/route.ts
  • apps/web/app/(ee)/api/google-ads/conversion-actions/route.ts
  • apps/web/app/(ee)/api/google-ads/upload-conversion/route.ts
  • apps/web/app/api/integrations/uninstall/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/integrations/google-ads/api.ts
  • apps/web/lib/integrations/google-ads/installed-workspaces.ts
  • apps/web/lib/integrations/google-ads/oauth.ts
  • apps/web/lib/integrations/google-ads/schema.ts
  • apps/web/lib/integrations/google-ads/ui/settings.tsx
  • apps/web/lib/integrations/google-ads/upload-conversion.ts
  • apps/web/vercel.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • apps/web/vercel.json
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx
  • apps/web/lib/integrations/google-ads/schema.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/integrations/google-ads/installed-workspaces.ts
  • apps/web/app/api/integrations/uninstall/route.ts
  • apps/web/lib/integrations/google-ads/ui/settings.tsx
  • apps/web/lib/integrations/google-ads/oauth.ts
  • apps/web/lib/integrations/google-ads/api.ts

Comment thread apps/web/lib/integrations/google-ads/upload-conversion.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant