Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Sep 9, 2025

Summary by CodeRabbit

  • New Features

    • Lead tracking endpoint now accepts a clickId parameter so clients can attach click identifiers for attribution.
    • ID fields (customerExternalId, externalId, customerId) accept trimmed string input for more consistent handling.
  • Bug Fixes

    • Input is normalized so missing/null clickId is treated as an empty string, improving robustness and preventing null/undefined issues.

@vercel
Copy link
Contributor

vercel bot commented Sep 9, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 9, 2025 8:25pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Walkthrough

Adds a clickId field to the POST /api/track/lead route schema (route-level accepts nullish), normalizes clickId to a non-null string before calling trackLead, tightens the exported Zod schema to require a trimmed string for clickId, and updates a comment in the track-lead conversion file to reflect empty-string semantics.

Changes

Cohort / File(s) Summary
API route & handler
apps/web/app/(ee)/api/track/lead/route.ts
Adds clickId: z.string().trim().nullish() to the route request schema and forwards clickId as clickId ?? "" to trackLead (ensures a non-null string).
Leads Zod schema
apps/web/lib/zod/schemas/leads.ts
Changes exported trackLeadRequestSchema.clickId from z.string().trim().nullish() to z.string().trim() and updates description to use empty string as the sentinel.
Conversion tracking implementation
apps/web/lib/api/conversions/track-lead.ts
Minor comment update clarifying the guard handles an empty-string clickId; no functional or control-flow changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as POST /api/track/lead
  participant Converter as trackLead()

  Client->>API: POST { ..., clickId?: string|null|undefined }
  note right of API #8fbf8f: Route-level schema accepts nullish\nthen normalizes to non-null string
  API->>API: clickId = clickId ?? ""
  API->>Converter: trackLead({ clickId: "", ... }) when missing/null
  alt clickId == ""
    Converter-->>API: handle empty-clickId lookup/path
  else clickId != ""
    Converter-->>API: proceed with provided clickId
  end
  API-->>Client: Response
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • devkiran

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning This pull request currently lacks any description, providing no context for the schema adjustments, coercion logic, or backward-compatibility updates made to the clickId and related fields. Without a description, reviewers must infer the intent from the code diff alone, which can lead to misunderstandings. Please add a brief description summarizing the schema modification that makes clickId required, the updates to backward-compat fields, and the handling of nullish or empty values so that reviewers can quickly grasp the changeset’s purpose.
βœ… Passed checks (2 passed)
Check name Status Explanation
Title Check βœ… Passed The title β€œMake clickId required in SDK” accurately and succinctly captures the main change of the pull request, which is altering the schema to enforce a non-null string clickId. It is specific and clear, avoiding unnecessary details or ambiguous terms, and will help collaborators understand the purpose at a glance.
Docstring Coverage βœ… Passed No functions found in the changes. Docstring coverage check skipped.

Poem

A nibble, a twitch, a tidy clue,
Click IDs trimmed to something new.
Empty or filled, I hop the trail,
Normalize, forward β€” follow the tail.
Thump! The burrow's code is true. πŸ‡βœ¨


πŸ“œ Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 3fdcd40 and 004e7db.

πŸ“’ Files selected for processing (1)
  • apps/web/app/(ee)/api/track/lead/route.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/api/track/lead/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Vade Review
  • GitHub Check: build
✨ Finishing Touches
  • πŸ“ Generate Docstrings
πŸ§ͺ Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clickid-required

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (1)
apps/web/lib/api/conversions/track-lead.ts (1)

52-60: Align error message with new "empty string sentinel" semantics

The guard now explicitly treats empty string as "missing"; update the error text to avoid confusion.

-          "The `clickId` property was not provided in the request, and no existing customer with the provided `customerExternalId` was found.",
+          "The `clickId` was empty or missing, and no existing customer with the provided `customerExternalId` was found.",
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 7b6839b and 3fdcd40.

πŸ“’ Files selected for processing (3)
  • apps/web/app/(ee)/api/track/lead/route.ts (2 hunks)
  • apps/web/lib/api/conversions/track-lead.ts (1 hunks)
  • apps/web/lib/zod/schemas/leads.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Vade Review
  • GitHub Check: build
πŸ”‡ Additional comments (1)
apps/web/lib/zod/schemas/leads.ts (1)

8-13: LGTM: clickId now required with empty-string sentinel

The description matches the new contract. Ensure callers that still pass nullish values coerce/trim to "" before invoking business logic.

If your API docs are generated from this schema, they’ll show clickId as required. Please confirm your docs generator uses this file (and not the route-local schema that marks it nullish). If unsure, I can help verify.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

apps/web/app/(ee)/api/track/lead/client/route.ts (lines 40-40):

The client route will now reject requests with missing or null clickId values, breaking backwards compatibility with existing integrations.

View Details
πŸ“ Patch Details
diff --git a/apps/web/app/(ee)/api/track/lead/client/route.ts b/apps/web/app/(ee)/api/track/lead/client/route.ts
index 03ad1093c..588bfd2cb 100644
--- a/apps/web/app/(ee)/api/track/lead/client/route.ts
+++ b/apps/web/app/(ee)/api/track/lead/client/route.ts
@@ -9,6 +9,7 @@ import { parseRequestBody } from "@/lib/api/utils";
 import { withPublishableKey } from "@/lib/auth/publishable-key";
 import { trackLeadRequestSchema } from "@/lib/zod/schemas/leads";
 import { NextResponse } from "next/server";
+import { z } from "zod";
 
 // POST /api/track/lead/client – Track a lead conversion event on the client side
 export const POST = withPublishableKey(
@@ -37,10 +38,17 @@ export const POST = withPublishableKey(
       customerAvatar,
       mode,
       metadata,
-    } = trackLeadRequestSchema.parse(body);
+    } = trackLeadRequestSchema
+      .extend({
+        // we if clickId is undefined/nullish, we'll coerce into an empty string
+        clickId: z.string().nullish(),
+        // add backwards compatibility
+        customerExternalId: z.string().nullish(),
+      })
+      .parse(body);
 
     const response = await trackLead({
-      clickId,
+      clickId: clickId ?? "",
       eventName,
       eventQuantity,
       customerExternalId,

Analysis

API Route Inconsistency: clickId Field Validation Bug

Summary

A backwards compatibility issue exists between two lead tracking API routes where the /api/track/lead/client endpoint rejects requests with null or missing clickId values that the main /api/track/lead endpoint accepts.

Technical Details

Root Cause

The issue stems from inconsistent schema validation patterns:

  • Main route (/api/track/lead/route.ts): Uses an extended schema that allows clickId to be nullish() for backwards compatibility
  • Client route (/api/track/lead/client/route.ts): Uses the base trackLeadRequestSchema directly, which requires clickId as a non-null string

Schema Validation Behavior

The base trackLeadRequestSchema defines clickId as:

clickId: z.string().trim()

This requires a string value and rejects null, undefined, or missing values.

The main route extends this schema:

trackLeadRequestSchema.extend({
  clickId: z.string().nullish(),
  // other backwards compatibility fields...
})

This allows null and undefined values, which are then coerced to empty strings.

Validation Evidence

Testing confirmed the inconsistency:

  • Base schema (client route): ❌ Rejects null clickId with "Expected string, received null"
  • Base schema (client route): ❌ Rejects missing clickId with "Required"
  • Extended schema (main route): βœ… Accepts both null and missing clickId values

Impact

Breaking Change for Client Integrations: Existing client applications using /api/track/lead/client that previously omitted the clickId field or sent null values will now receive validation errors, potentially breaking their lead tracking functionality.

API Inconsistency: The same request payload behaves differently across the two endpoints, violating the principle of least surprise and creating confusion for developers.

Solution

Applied the same schema extension pattern used in the main route to the client route:

  1. Added Zod import: import { z } from "zod";

  2. Extended the schema: Applied the same .extend() pattern with clickId: z.string().nullish()

  3. Added null coercion: Convert null/undefined clickId values to empty strings: clickId: clickId ?? ""

This ensures both routes handle clickId consistently while maintaining backwards compatibility with existing integrations.

References

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

apps/web/app/(ee)/api/track/lead/client/route.ts (lines 40-40):

The client route is broken because it uses the base schema that now requires clickId, but the intent is to make clickId optional.

View Details
πŸ“ Patch Details
diff --git a/apps/web/app/(ee)/api/track/lead/client/route.ts b/apps/web/app/(ee)/api/track/lead/client/route.ts
index 03ad1093c..0d4b3c857 100644
--- a/apps/web/app/(ee)/api/track/lead/client/route.ts
+++ b/apps/web/app/(ee)/api/track/lead/client/route.ts
@@ -9,6 +9,7 @@ import { parseRequestBody } from "@/lib/api/utils";
 import { withPublishableKey } from "@/lib/auth/publishable-key";
 import { trackLeadRequestSchema } from "@/lib/zod/schemas/leads";
 import { NextResponse } from "next/server";
+import { z } from "zod";
 
 // POST /api/track/lead/client – Track a lead conversion event on the client side
 export const POST = withPublishableKey(
@@ -37,10 +38,15 @@ export const POST = withPublishableKey(
       customerAvatar,
       mode,
       metadata,
-    } = trackLeadRequestSchema.parse(body);
+    } = trackLeadRequestSchema
+      .extend({
+        // we if clickId is undefined/nullish, we'll coerce into an empty string
+        clickId: z.string().trim().nullish(),
+      })
+      .parse(body);
 
     const response = await trackLead({
-      clickId,
+      clickId: clickId ?? "",
       eventName,
       eventQuantity,
       customerExternalId,

Analysis

API Inconsistency: Client Route Requires Optional clickId Field

Bug Summary

The /api/track/lead/client endpoint incorrectly requires the clickId field, while the main /api/track/lead endpoint correctly treats it as optional. This creates an API inconsistency and breaks existing clients that don't provide clickId.

Technical Analysis

Schema Definition

The base trackLeadRequestSchema in apps/web/lib/zod/schemas/leads.ts defines clickId as:

clickId: z.string().trim()

This makes clickId required by default (no .nullish() or .optional()).

Route Implementations

Main Route (/api/track/lead):

  • βœ… Correctly handles optional clickId
  • Uses trackLeadRequestSchema.extend({ clickId: z.string().trim().nullish() })
  • Provides fallback: clickId: clickId ?? ""

Client Route (/api/track/lead/client):

  • ❌ Incorrectly requires clickId
  • Uses trackLeadRequestSchema.parse(body) directly
  • No schema extension to make clickId optional

Validation Results

Testing confirmed the issue using Zod schema validation:

// Client route behavior (BEFORE fix)
trackLeadRequestSchema.parse({
  eventName: "Sign up",
  customerExternalId: "user123"
  // No clickId provided
});
// ❌ Error: "Required"

// Main route behavior 
trackLeadRequestSchema.extend({ 
  clickId: z.string().trim().nullish() 
}).parse({
  eventName: "Sign up", 
  customerExternalId: "user123"
});
// βœ… Success: clickId = undefined

Impact

  • Breaking Change: Existing clients using /api/track/lead/client without clickId will fail with validation errors
  • API Inconsistency: Two endpoints with different requirements for the same field
  • Developer Confusion: Inconsistent behavior between similar endpoints

Solution

Applied the same schema extension pattern used in the main route to the client route:

  1. Added Zod import: import { z } from "zod"
  2. Extended schema: Used .extend({ clickId: z.string().trim().nullish() })
  3. Added fallback: Used clickId: clickId ?? "" in the trackLead call

This ensures both endpoints behave identically and maintain backward compatibility.

Verification

Post-fix testing shows both routes now handle optional clickId consistently:

  • βœ… Requests without clickId succeed (coerced to empty string)
  • βœ… Requests with clickId work as before
  • βœ… No breaking changes for existing clients

@steven-tey steven-tey merged commit a83eb20 into main Sep 9, 2025
8 of 9 checks passed
@steven-tey steven-tey deleted the clickid-required branch September 9, 2025 20:30
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.

2 participants