AppsFlyer integration - #3588
Conversation
- Detect AppsFlyer OneLink URLs (*.onelink.me) and inject clickid, af_siteid, af_ip, af_ua params on redirect - Cache clickId for AppsFlyer tracking URLs to support conversion matching - Add webhook endpoint (GET /api/appsflyer/webhook) authenticated via publishable_key - Support lead and sale event tracking from AppsFlyer postbacks - Add redirect test for AppsFlyer parameter injection
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds AppsFlyer integration across backend and frontend: webhook endpoint, AppsFlyer settings UI and server actions, parameter loading/apply utilities, partner-link generation and cron flows updated to inject AppsFlyer parameters, middleware handling for AppsFlyer URLs, constants, tests, and installer script updates. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Webhook as AppsFlyer Webhook
participant Prisma
participant Tracker as Tracking Service
participant Response
Client->>Webhook: GET /api/appsflyer/webhook?appId=...&partnerEventId=...
Webhook->>Webhook: resolve caller IP\nvalidate against APPSFLYER_IP_RANGES
alt IP not allowed
Webhook->>Response: return 403 forbidden (DubApiError)
else IP allowed
Webhook->>Webhook: parse & validate query params
Webhook->>Prisma: find installedIntegration by integrationId & appId in settings
alt not found
Webhook->>Response: return bad_request
else found
alt partnerEventId == "lead"
Webhook->>Webhook: validate lead payload
Webhook->>Tracker: trackLead(parsed..., workspace, rawBody)
else partnerEventId == "sale"
Webhook->>Webhook: validate sale payload
Webhook->>Tracker: trackSale(parsed..., workspace, rawBody)
else
Webhook->>Response: return JSON (ignored event)
end
Tracker->>Response: return success JSON
end
end
sequenceDiagram
participant Cron as Cron Job
participant AFUtil as loadAppsFlyerParameters
participant LinkGen as generatePartnerLink
participant URL as applyAppsFlyerParameters
participant DB as Prisma
Cron->>Cron: identify default link URLs
alt isAppsFlyerTrackingUrl detected
Cron->>AFUtil: loadAppsFlyerParameters(workspaceId)
AFUtil->>DB: query installedIntegration.settings
DB-->>AFUtil: return parameters
AFUtil-->>Cron: return parameters array
else
Cron->>Cron: use empty parameters
end
Cron->>LinkGen: generatePartnerLink(..., appsFlyerParameters)
LinkGen->>LinkGen: processLink => processedLink.url
alt appsFlyerParameters present & URL is AppsFlyer
LinkGen->>URL: applyAppsFlyerParameters(url, parameters, context)
URL->>URL: resolve macros & set search params
URL-->>LinkGen: return rewritten URL
end
LinkGen->>DB: create/update link record
DB-->>Cron: confirmation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
…th direct API calls for lead and sale events, update query parameter schema, and remove obsolete tracking files. Add a new script for creating AppsFlyer postback requests.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/web/app/(ee)/api/appsflyer/webhook/route.ts (1)
16-20:⚠️ Potential issue | 🔴 CriticalWebhook auth is still appId-only; bind requests to
publishable_keyas well.Line 16 and Line 52 currently authenticate by
appIdmembership only. SinceappIdis non-secret, any caller from allowed AppsFlyer IP space can target another installation if they know/guess anappId.🔒 Suggested direction
const querySchema = z.object({ appId: z.string(), + publishable_key: z.string().min(1, "publishable_key is required"), partnerEventId: z.string(), eventValue: z.string().nullish(), }); -const { appId, partnerEventId } = querySchema.parse(queryParams); +const { appId, publishable_key, partnerEventId } = querySchema.parse(queryParams); const installation = await prisma.installedIntegration.findFirst({ where: { integrationId: APPSFLYER_INTEGRATION_ID, settings: { path: "$.appIds", array_contains: appId, }, + // also validate installation/workspace-bound publishable key + // (exact field path depends on where this key is persisted) },Also applies to: 52-59
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts around lines 16 - 20, Update the webhook request validation and auth to bind requests to the publishable_key in addition to appId: extend the existing Zod querySchema to require a publishable_key (e.g., add publishable_key: z.string()) and then change the authentication logic that currently authorizes by appId-only to verify that the provided publishable_key belongs to the same installation as the appId before processing (update the route handler/auth check where it looks up or validates appId membership so it validates both appId and publishable_key together). Ensure any error messages/logging reference both identifiers and reject requests where the publishable_key does not match the appId.
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/appsflyer/webhook/route.ts (1)
79-87: Lead parsing can be made more robust for query-string numeric fields.
trackLeadRequestSchema.parse(queryParams)will fail if optional numeric fields (for exampleeventQuantity) arrive as strings in the URL. Consider parsing only the used fields (or coercing known numeric fields) to avoid brittle webhook failures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts around lines 79 - 87, partnerEventId handling is fine but trackLeadRequestSchema.parse(queryParams) can fail when numeric fields (e.g., eventQuantity) arrive as strings; modify the parsing to coerce known numeric query params before validating (or use a schema coercion/preprocess for those fields) so optional numeric fields won't break the webhook: identify the numeric fields (like eventQuantity) in the incoming queryParams, convert them to numbers when present (or apply zod/coercion on trackLeadRequestSchema) and then call trackLeadRequestSchema.parse on the normalized input instead of raw queryParams.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts:
- Around line 16-20: Update the webhook request validation and auth to bind
requests to the publishable_key in addition to appId: extend the existing Zod
querySchema to require a publishable_key (e.g., add publishable_key: z.string())
and then change the authentication logic that currently authorizes by appId-only
to verify that the provided publishable_key belongs to the same installation as
the appId before processing (update the route handler/auth check where it looks
up or validates appId membership so it validates both appId and publishable_key
together). Ensure any error messages/logging reference both identifiers and
reject requests where the publishable_key does not match the appId.
---
Nitpick comments:
In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts:
- Around line 79-87: partnerEventId handling is fine but
trackLeadRequestSchema.parse(queryParams) can fail when numeric fields (e.g.,
eventQuantity) arrive as strings; modify the parsing to coerce known numeric
query params before validating (or use a schema coercion/preprocess for those
fields) so optional numeric fields won't break the webhook: identify the numeric
fields (like eventQuantity) in the incoming queryParams, convert them to numbers
when present (or apply zod/coercion on trackLeadRequestSchema) and then call
trackLeadRequestSchema.parse on the normalized input instead of raw queryParams.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f16ac3d3-ac4c-4f82-992a-8036a2a5adff
📒 Files selected for processing (1)
apps/web/app/(ee)/api/appsflyer/webhook/route.ts
Allow workspaces to customize the values of c and af_siteid parameters
which are always included in AppsFlyer links. These default to
{{PARTNER_NAME}} and {{PARTNER_LINK_KEY}} respectively but can be
changed. Also triggers re-application of parameters to existing links
when settings change and normalizes input by trimming whitespace.
|
@coderabbitai full review, any breaking changes? |
🧩 Analysis chain🏁 Script executed: Length of output: 17434 🏁 Script executed: Length of output: 6280 🏁 Script executed: Length of output: 7874
✅ No Breaking Changes to Existing IntegrationsAll changes are additive. Existing Stripe, HubSpot, Singular, and other integrations are unaffected. The one behavioral change in
|
| Issue | Severity | Breaking? |
|---|---|---|
| Hardcoded IP ranges may become stale | High | No (silent auth failures) |
Promise.allSettled silent failures |
Medium | No |
Dual dub_id + clickid injection |
Medium | No |
pid unconditionally overridden |
Low | No |
create-integration.ts ID drift |
Medium | No (deployment risk) |
| Custom OneLink domains not detected | Low | No |
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
apps/web/app/(ee)/api/appsflyer/webhook/route.ts (1)
16-20:⚠️ Potential issue | 🔴 CriticalRequire
publishable_keyfor installation lookup.The handler still authenticates on shared AppsFlyer IPs plus
appIdonly.appIdis not secret, so an allowed sender that knows an app ID can target another workspace, andfindFirst()stays ambiguous if IDs collide. Addpublishable_keytoquerySchemaand match on it in the Prisma lookup.Also applies to: 47-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts around lines 16 - 20, The handler currently authenticates using only appId (in querySchema) which is public; add publishable_key: z.string() to the querySchema and update the Prisma lookup (the call that uses findFirst()) to include publishable_key in its where clause alongside appId so the installation lookup matches both appId and publishable_key (e.g., use where: { appId: appId, publishable_key: publishable_key }) to prevent ambiguous/incorrect workspace matches.apps/web/lib/integrations/appsflyer/update-settings.ts (1)
55-57:⚠️ Potential issue | 🟠 MajorDon't let malformed stored settings block the repair path.
InstalledIntegration.settingsis free-form JSON.parse()here turns a stale/corrupt blob into a hard failure, so the workspace can't save corrected AppsFlyer settings.Suggested fix
- const current = appsFlyerSettingsSchema.parse( - installedIntegration.settings ?? {}, - ); + const parsedCurrent = appsFlyerSettingsSchema.safeParse( + installedIntegration.settings ?? {}, + ); + const current = parsedCurrent.success + ? parsedCurrent.data + : appsFlyerSettingsSchema.parse({});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/integrations/appsflyer/update-settings.ts` around lines 55 - 57, The call to appsFlyerSettingsSchema.parse on InstalledIntegration.settings can throw on stale/corrupt JSON and block saving; change the code to validate safely (e.g., use appsFlyerSettingsSchema.safeParse or wrap parse in try/catch) and fall back to an empty/default settings object when validation fails so the repair flow can proceed; update the assignment to current (the variable holding parsed settings) to use the safe result or default and optionally log a warning about the malformed stored settings.apps/web/app/(ee)/api/cron/groups/update-default-links/route.ts (1)
181-191:⚠️ Potential issue | 🟠 MajorFail the batch when any per-link update rejects.
Promise.allSettled()is still treated as success here. That makes the success log, cache expiry, and cursor advance lie about progress, so failed links get skipped until someone reruns the job.Suggested fix
if (linksToUpdate.length > 0) { - await Promise.allSettled( + const results = await Promise.allSettled( linksToUpdate.map(({ id, link }) => prisma.link.update({ where: { id, }, data: link, }), ), ); + + const rejectedLinkIds = results.flatMap((result, index) => + result.status === "rejected" ? [linksToUpdate[index].id] : [], + ); + + if (rejectedLinkIds.length > 0) { + throw new Error( + `Failed to update default partner links: ${rejectedLinkIds.join(", ")}`, + ); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/`(ee)/api/cron/groups/update-default-links/route.ts around lines 181 - 191, The batch currently uses Promise.allSettled on linksToUpdate (mapping to prisma.link.update) but treats the overall result as success; change this so any per-link rejection fails the batch: either replace Promise.allSettled with Promise.all so a single rejection will propagate, or keep allSettled but immediately inspect the settled results and throw an Error if any result.status === "rejected"; ensure this change is made around the linksToUpdate -> prisma.link.update block so the surrounding success log, cache expiry, and cursor advance only run when all updates succeeded.
🧹 Nitpick comments (4)
apps/web/lib/middleware/utils/get-final-url.ts (2)
54-68: Dualdub_idandclickidinjection for AppsFlyer URLs.When the destination is an AppsFlyer tracking URL and
clickIdis present, bothdub_id(Line 55) andclickid(Line 68) are set to the same value. This is redundant for AppsFlyer URLs since AppsFlyer usesclickidfor attribution.Consider skipping
dub_idinjection when the URL is an AppsFlyer tracking URL:♻️ Proposed fix
} else if (!searchParams.has("dub-no-track")) { - urlObj.searchParams.set("dub_id", clickId); + // Skip dub_id for AppsFlyer URLs since they use clickid instead + if (!isAppsFlyerTrackingUrl(url)) { + urlObj.searchParams.set("dub_id", clickId); + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/middleware/utils/get-final-url.ts` around lines 54 - 68, The code currently injects dub_id then later injects clickid for AppsFlyer links causing redundant params; modify the dub_id injection to skip setting dub_id when the destination is an AppsFlyer tracking URL by adding a check using isAppsFlyerTrackingUrl(url) (or isAppsFlyerTrackingUrl(urlObj)) in the block that sets urlObj.searchParams.set("dub_id", clickId) so dub_id is only added when not an AppsFlyer URL and still respect the existing !searchParams.has("dub-no-track") and clickId conditions.
64-65: Add a comment documenting the hardcodedpidoverride.Line 65 unconditionally sets
pid=dubinc_int, which overwrites any existingpidvalue in user-configured AppsFlyer URLs. If this is intentional for Dub attribution, a brief comment would clarify the intent for future maintainers.📝 Suggested documentation
- // set hardcoded query params - urlObj.searchParams.set("pid", "dubinc_int"); + // Hardcode pid to identify traffic from Dub (overwrites any existing pid) + urlObj.searchParams.set("pid", "dubinc_int");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/middleware/utils/get-final-url.ts` around lines 64 - 65, Add a short explanatory comment above the hardcoded override that sets pid to "dubinc_int" in get-final-url (i.e., the line using urlObj.searchParams.set("pid", "dubinc_int")). The comment should state that this unconditional overwrite is intentional for Dub attribution (or note any conditions/links to spec), and warn that it will replace user-provided AppsFlyer pid values so future maintainers understand the reason and can safely change or remove it.apps/web/app/(ee)/api/cron/groups/remap-default-links/route.ts (1)
147-184: Consider logging rejected promises fromPromise.allSettled.The
.filter(isFulfilled)silently discards anygeneratePartnerLinkcalls that fail. WhilePromise.allSettledis appropriate for resilience (one failure shouldn't block others), capturing and logging rejected results would help with debugging and monitoring.♻️ Proposed enhancement to log failures
const processedLinks = ( await Promise.allSettled( linksToCreate.map((link) => { // ... existing code }), ) - ) - .filter(isFulfilled) - .map(({ value }) => value); + ); + + const fulfilled = processedLinks.filter(isFulfilled).map(({ value }) => value); + const rejected = processedLinks.filter( + (r): r is PromiseRejectedResult => r.status === "rejected", + ); + + if (rejected.length > 0) { + console.error( + `Failed to generate ${rejected.length} partner links:`, + rejected.map((r) => r.reason), + ); + } const createdLinks = await bulkCreateLinks({ - links: processedLinks, + links: fulfilled, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/`(ee)/api/cron/groups/remap-default-links/route.ts around lines 147 - 184, The Promise.allSettled for linksToCreate currently filters only fulfilled results (via isFulfilled) and silently drops rejections; change the post-processing to capture and log rejected results from the settled array before mapping fulfilled values: after awaiting Promise.allSettled(...) iterate the settled results, log each rejected entry (including the associated link metadata like link.partnerGroupDefaultLinkId, link.domain or link.url and the rejection reason/error) using the existing logger (or console) so failures from generatePartnerLink are visible, then continue to filter isFulfilled and map ({ value }) => value to build processedLinks.apps/web/lib/api/partners/create-partner-default-links.ts (1)
60-91: SamePromise.allSettledsilent failure concern.Consider logging rejected promises here as well for consistency and debuggability. The same pattern improvement suggested for
remap-default-links/route.tswould apply here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/api/partners/create-partner-default-links.ts` around lines 60 - 91, The Promise.allSettled in processedLinks silently drops rejections; after awaiting Promise.allSettled for the buildPartnerDefaultLinkKey/generatePartnerLink loop, add handling to log any rejected results with contextual data (defaultLink.id, key, partner, link info) and the rejection reason so failures aren’t hidden—use the existing logger (e.g., processLogger or logger) to emit error/warn messages before filtering with isFulfilled and mapping to value; this keeps generatePartnerLink failures visible and aids debugging of constructURLFromUTMParams/extractUtmParams related errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/lib/integrations/appsflyer/constants.ts`:
- Line 42: The APPSFLYER_IP_RANGES constant is hardcoded and will break when
AppsFlyer rotates CIDRs; change APPSFLYER_IP_RANGES to be loaded from a managed
configuration source (e.g., environment variable, remote config service, or
feature-flag store) with a sensible default fallback and validation. Implement a
loader function (e.g., getAppsflyerIpRanges or loadAppsflyerConfig) that parses
and validates CIDRs, expose that value where APPSFLYER_IP_RANGES is currently
used, and add a periodic refresh + logging/alerting hook so rotations are
detected and operators are notified if validation fails. Ensure existing callers
of APPSFLYER_IP_RANGES are updated to use the loader and that the default array
remains available only as a fallback.
In `@apps/web/lib/integrations/appsflyer/ui/settings.tsx`:
- Around line 92-95: Replace the unsafe schema.parse calls with schema.safeParse
and fall back to defaults when validation fails: use
appsFlyerSettingsSchema.safeParse({...APPSFLYER_DEFAULT_SETTINGS, ...(settings
as any)}) and if result.success is false, assign APPSFLYER_DEFAULT_SETTINGS (or
a merged default) to appsFlyerSettings; do the same for the second parse block
(lines 111-121) so invalid stored macro templates or blank values do not throw
and users can recover to defaults. Ensure you still use the validated data when
result.success is true.
In `@apps/web/lib/integrations/appsflyer/update-settings.ts`:
- Around line 96-107: The current publish loop uses Promise.allSettled but
ignores failures, so any rejected qstash.publishJSON calls for
appsFlyerDefaultLinks silently succeed; update the block that maps
appsFlyerDefaultLinks and calls qstash.publishJSON to collect the allSettled
results, filter for rejected entries and either throw an error containing the
failed defaultLinkId(s) or call the workspace logger to record those
defaultLinkId values and the rejection reasons; reference the
appsFlyerDefaultLinks array, qstash.publishJSON calls, APP_DOMAIN_WITH_NGROK URL
and defaultLink.id when building the failure report so the code can surface
which defaultLinkIds need retrying.
In `@apps/web/scripts/create-integration.ts`:
- Around line 7-20: The integration ID is generated with createId() while
APPSFLYER_INTEGRATION_ID is a fixed constant, which causes ID drift on re-runs;
update the prisma.integration.create call to either use the
APPSFLYER_INTEGRATION_ID constant for the id field (if this is a one-time
bootstrap) or replace the create() with an upsert using
prisma.integration.upsert keyed on the slug or APPSFLYER_INTEGRATION_ID so
re-running the script updates the existing record instead of creating a new one;
ensure references to createId(), prisma.integration.create,
APPSFLYER_INTEGRATION_ID and DUB_WORKSPACE_ID are adjusted accordingly.
---
Duplicate comments:
In `@apps/web/app/`(ee)/api/appsflyer/webhook/route.ts:
- Around line 16-20: The handler currently authenticates using only appId (in
querySchema) which is public; add publishable_key: z.string() to the querySchema
and update the Prisma lookup (the call that uses findFirst()) to include
publishable_key in its where clause alongside appId so the installation lookup
matches both appId and publishable_key (e.g., use where: { appId: appId,
publishable_key: publishable_key }) to prevent ambiguous/incorrect workspace
matches.
In `@apps/web/app/`(ee)/api/cron/groups/update-default-links/route.ts:
- Around line 181-191: The batch currently uses Promise.allSettled on
linksToUpdate (mapping to prisma.link.update) but treats the overall result as
success; change this so any per-link rejection fails the batch: either replace
Promise.allSettled with Promise.all so a single rejection will propagate, or
keep allSettled but immediately inspect the settled results and throw an Error
if any result.status === "rejected"; ensure this change is made around the
linksToUpdate -> prisma.link.update block so the surrounding success log, cache
expiry, and cursor advance only run when all updates succeeded.
In `@apps/web/lib/integrations/appsflyer/update-settings.ts`:
- Around line 55-57: The call to appsFlyerSettingsSchema.parse on
InstalledIntegration.settings can throw on stale/corrupt JSON and block saving;
change the code to validate safely (e.g., use appsFlyerSettingsSchema.safeParse
or wrap parse in try/catch) and fall back to an empty/default settings object
when validation fails so the repair flow can proceed; update the assignment to
current (the variable holding parsed settings) to use the safe result or default
and optionally log a warning about the malformed stored settings.
---
Nitpick comments:
In `@apps/web/app/`(ee)/api/cron/groups/remap-default-links/route.ts:
- Around line 147-184: The Promise.allSettled for linksToCreate currently
filters only fulfilled results (via isFulfilled) and silently drops rejections;
change the post-processing to capture and log rejected results from the settled
array before mapping fulfilled values: after awaiting Promise.allSettled(...)
iterate the settled results, log each rejected entry (including the associated
link metadata like link.partnerGroupDefaultLinkId, link.domain or link.url and
the rejection reason/error) using the existing logger (or console) so failures
from generatePartnerLink are visible, then continue to filter isFulfilled and
map ({ value }) => value to build processedLinks.
In `@apps/web/lib/api/partners/create-partner-default-links.ts`:
- Around line 60-91: The Promise.allSettled in processedLinks silently drops
rejections; after awaiting Promise.allSettled for the
buildPartnerDefaultLinkKey/generatePartnerLink loop, add handling to log any
rejected results with contextual data (defaultLink.id, key, partner, link info)
and the rejection reason so failures aren’t hidden—use the existing logger
(e.g., processLogger or logger) to emit error/warn messages before filtering
with isFulfilled and mapping to value; this keeps generatePartnerLink failures
visible and aids debugging of constructURLFromUTMParams/extractUtmParams related
errors.
In `@apps/web/lib/middleware/utils/get-final-url.ts`:
- Around line 54-68: The code currently injects dub_id then later injects
clickid for AppsFlyer links causing redundant params; modify the dub_id
injection to skip setting dub_id when the destination is an AppsFlyer tracking
URL by adding a check using isAppsFlyerTrackingUrl(url) (or
isAppsFlyerTrackingUrl(urlObj)) in the block that sets
urlObj.searchParams.set("dub_id", clickId) so dub_id is only added when not an
AppsFlyer URL and still respect the existing !searchParams.has("dub-no-track")
and clickId conditions.
- Around line 64-65: Add a short explanatory comment above the hardcoded
override that sets pid to "dubinc_int" in get-final-url (i.e., the line using
urlObj.searchParams.set("pid", "dubinc_int")). The comment should state that
this unconditional overwrite is intentional for Dub attribution (or note any
conditions/links to spec), and warn that it will replace user-provided AppsFlyer
pid values so future maintainers understand the reason and can safely change or
remove it.
🪄 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: a6d6d4d6-75cd-452f-9dcf-7fcb9af3b8ee
📒 Files selected for processing (23)
apps/web/app/(ee)/api/appsflyer/webhook/route.tsapps/web/app/(ee)/api/cron/groups/create-default-links/route.tsapps/web/app/(ee)/api/cron/groups/remap-default-links/route.tsapps/web/app/(ee)/api/cron/groups/update-default-links/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page.tsxapps/web/lib/api/partners/create-partner-default-links.tsapps/web/lib/api/partners/generate-partner-link.tsapps/web/lib/integrations/appsflyer/apply-parameters.tsapps/web/lib/integrations/appsflyer/constants.tsapps/web/lib/integrations/appsflyer/install.tsapps/web/lib/integrations/appsflyer/macro-template.tsapps/web/lib/integrations/appsflyer/schema.tsapps/web/lib/integrations/appsflyer/ui/settings.tsxapps/web/lib/integrations/appsflyer/update-settings.tsapps/web/lib/middleware/link.tsapps/web/lib/middleware/utils/get-final-url.tsapps/web/lib/middleware/utils/is-appsflyer-tracking-url.tsapps/web/lib/plan-capabilities.tsapps/web/scripts/create-integration.tsapps/web/scripts/test-appsflyer.tsapps/web/tests/redirects/index.test.tspackages/utils/src/constants/integrations.ts
Summary by CodeRabbit
New Features
Partner Links
Background Jobs
Middleware
Tests
Chores