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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Nov 5, 2025

Summary by CodeRabbit

  • New Features

    • Program-level webhook triggers added (including partner.application_submitted).
    • Webhooks now emit partner application submissions with enriched payloads: partner + enrollment status, normalized website/social fields, and structured application form data.
    • New Slack notification template and sample event for partner application submissions.
  • UI

    • Webhook configuration UI surfaces program-level triggers when a program context exists.
  • Tests

    • Schema/tests registered for partner.application_submitted.

@vercel
Copy link
Contributor

vercel bot commented Nov 5, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 6, 2025 8:25pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds a program-level webhook event partner.application_submitted, threads workspace info through program application/enrollment flows, formats application and website/social fields, emits workspace webhooks for submissions, and updates schemas, Slack templates, UI trigger selection, samples, and tests.

Changes

Cohort / File(s) Summary
Webhook constants & schemas
apps/web/lib/webhook/constants.ts, apps/web/lib/webhook/schemas.ts, apps/web/lib/webhook/types.ts, apps/web/lib/zod/schemas/program-application.ts
Introduce PROGRAM_LEVEL_WEBHOOK_TRIGGERS and include it in WEBHOOK_TRIGGERS; add partner.application_submitted description; add partnerApplicationWebhookSchema and expose PartnerApplicationWebhookPayload; extend webhook event union with new event variant.
Application creation & completion
apps/web/lib/actions/partners/create-program-application.ts, apps/web/lib/partners/complete-program-applications.ts
Thread `workspace: Pick<Project,"id"
Formatting utilities
apps/web/lib/partners/format-application-form-data.ts
Add FormDataKeyValue interface, change formatApplicationFormData return type to it, and add formatWebsiteAndSocialsFields to normalize website/socials values from program applications.
Slack integration
apps/web/lib/integrations/slack/transform.ts
Add partnerApplicationSubmittedTemplate and register it under partner.application_submitted for Slack notifications.
Webhook UI / trigger selection
apps/web/lib/integrations/common/ui/configure-webhook.tsx, apps/web/ui/webhooks/add-edit-webhook-form.tsx
Surface PROGRAM_LEVEL_WEBHOOK_TRIGGERS, combine program-level triggers with workspace-level triggers when defaultProgramId exists, and render combined trigger list (preserving existing filtering/enablement logic).
Sample events & mapping
apps/web/lib/webhook/sample-events/partner-application-submitted.json, apps/web/lib/webhook/sample-events/payload.ts
Add sample payload for partner.application_submitted and register it in samplePayload.
Tests
apps/web/tests/webhooks/index.test.ts
Register partnerApplicationWebhookSchema in test eventSchemas mapping for the new event.
Minor / samples & schema cleanup
apps/web/lib/webhook/sample-events/partner-enrolled.json, apps/web/lib/zod/schemas/programs.ts
Update sample IDs in partner-enrolled.json; remove a TODO comment in programs schema (no type change).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Partner
    participant WebApp as Web App
    participant DB as Database
    participant WebhookSvc as Workspace Webhook Sender
    participant Integrations as Integrations (Slack, etc.)

    Partner->>WebApp: Submit program application
    WebApp->>DB: create application & program enrollment
    DB-->>WebApp: application + programEnrollment
    WebApp->>WebApp: formatApplicationFormData & formatWebsiteAndSocialsFields
    alt program.workspace?.webhookEnabled
        WebApp->>WebhookSvc: sendWorkspaceWebhook(event: partner.application_submitted, payload)
        WebhookSvc->>Integrations: transform & dispatch (e.g., Slack template)
        Integrations-->>WebhookSvc: delivery result
    end
    WebApp-->>Partner: confirm submission
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • Areas needing extra attention:
    • Zod schema composition and picks/merges in partnerApplicationWebhookSchema.
    • Call-site and signature changes for createApplicationAndEnrollment (workspace wiring).
    • Webhook emission conditions (workspace presence and webhookEnabled) and payload validation.
    • Slack template formatting and registration for the new event.
    • UI logic combining workspace and program triggers to ensure filtering rules preserved.

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐇 I hopped through forms and labeled each line,
Gathered socials and trimmed every vine,
A webhook took wing, payload snug and neat,
Slack sang the tale — partners marching to the beat! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main feature being added: a new webhook trigger event for partner application submissions.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch application-submitted-webhook-event

📜 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 2cb20af and 792a53e.

📒 Files selected for processing (15)
  • apps/web/lib/actions/partners/create-program-application.ts (8 hunks)
  • apps/web/lib/integrations/common/ui/configure-webhook.tsx (4 hunks)
  • apps/web/lib/integrations/slack/transform.ts (3 hunks)
  • apps/web/lib/partners/complete-program-applications.ts (4 hunks)
  • apps/web/lib/partners/format-application-form-data.ts (2 hunks)
  • apps/web/lib/webhook/constants.ts (2 hunks)
  • apps/web/lib/webhook/sample-events/partner-application-submitted.json (1 hunks)
  • apps/web/lib/webhook/sample-events/partner-enrolled.json (2 hunks)
  • apps/web/lib/webhook/sample-events/payload.ts (2 hunks)
  • apps/web/lib/webhook/schemas.ts (2 hunks)
  • apps/web/lib/webhook/types.ts (3 hunks)
  • apps/web/lib/zod/schemas/program-application.ts (1 hunks)
  • apps/web/lib/zod/schemas/programs.ts (1 hunks)
  • apps/web/tests/webhooks/index.test.ts (2 hunks)
  • apps/web/ui/webhooks/add-edit-webhook-form.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.

Applied to files:

  • apps/web/tests/webhooks/index.test.ts
  • apps/web/lib/webhook/types.ts
  • apps/web/lib/actions/partners/create-program-application.ts
  • apps/web/lib/zod/schemas/program-application.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/lib/zod/schemas/programs.ts
  • apps/web/lib/webhook/sample-events/partner-application-submitted.json
📚 Learning: 2025-08-14T05:00:23.224Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/app/api/og/program/route.tsx:63-64
Timestamp: 2025-08-14T05:00:23.224Z
Learning: In Dub's partner program system, the default partner group will always exist. When programs are created, a default partner group is automatically upserted using DEFAULT_PARTNER_GROUP constant, so accessing program.groups[0] in contexts where the default group is queried is safe.

Applied to files:

  • apps/web/lib/zod/schemas/programs.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/integrations/slack/transform.ts
  • apps/web/lib/webhook/constants.ts
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/ui/webhooks/add-edit-webhook-form.tsx
📚 Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/ui/webhooks/add-edit-webhook-form.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/webhook/sample-events/payload.ts
  • apps/web/lib/webhook/constants.ts
  • apps/web/lib/partners/complete-program-applications.ts
  • apps/web/lib/actions/partners/create-program-application.ts
  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/webhook/schemas.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/webhook/sample-events/payload.ts
  • apps/web/lib/partners/complete-program-applications.ts
📚 Learning: 2025-08-25T17:33:45.072Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts:12-12
Timestamp: 2025-08-25T17:33:45.072Z
Learning: The WorkflowTrigger enum in packages/prisma/schema/workflow.prisma contains three values: leadRecorded, saleRecorded, and commissionEarned. All three are properly used throughout the codebase.

Applied to files:

  • apps/web/lib/webhook/constants.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/webhook/sample-events/partner-application-submitted.json
  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/webhook/schemas.ts
🔇 Additional comments (26)
apps/web/lib/zod/schemas/program-application.ts (1)

5-41: LGTM!

The schema is well-structured and follows best practices:

  • Properly composes partner data from existing schemas using .pick() and .merge()
  • Nullable value in applicationFormData correctly handles optional form fields
  • Uses standard zod import as per coding guidelines

Based on coding guidelines.

apps/web/tests/webhooks/index.test.ts (1)

17-17: LGTM!

The new webhook event schema is correctly registered in the test suite, following the established pattern for webhook event validation.

Also applies to: 84-84

apps/web/lib/webhook/sample-events/partner-enrolled.json (1)

2-2: LGTM!

The ID format update to use the pn_ prefix improves sample data consistency and aligns with partner entity naming conventions.

Also applies to: 11-11

apps/web/lib/webhook/sample-events/payload.ts (1)

10-10: LGTM!

The new sample event is correctly imported and mapped, following the established pattern for webhook sample payloads.

Also applies to: 22-22

apps/web/lib/webhook/sample-events/partner-application-submitted.json (1)

1-35: Previous critical issue has been resolved.

The sample payload now correctly matches the partnerApplicationWebhookSchema structure with all required fields present:

  • ✓ Top-level id and createdAt
  • ✓ Complete partner object with enrollment and social fields
  • applicationFormData array with realistic form entries

The payload is now suitable for testing and documentation.

apps/web/lib/integrations/common/ui/configure-webhook.tsx (2)

8-8: LGTM!

The conditional inclusion of program-level webhook triggers based on defaultProgramId is correctly implemented. The spread operator safely handles the case when defaultProgramId is undefined, resulting in an empty array.

Also applies to: 29-29, 94-97


118-158: LGTM!

The UI correctly renders the extended trigger list while maintaining existing permission checks (canManageWebhook) and support filtering (supportedEvents).

apps/web/lib/integrations/slack/transform.ts (1)

12-12: LGTM!

The Slack notification template for partner applications is well-structured:

  • Follows the established pattern from other webhook templates
  • Properly typed with PartnerApplicationWebhookPayload
  • Conditionally includes optional fields (company name)
  • Constructs a valid deep link to the application view

Also applies to: 255-308, 544-544

apps/web/lib/zod/schemas/programs.ts (1)

88-88: Update schema to reflect complete migration or fix bulk import flow.

The TODO comment about updating groupId to required after migration has been removed, but the field remains .nullish().

The primary enrollment creation path (create-program-application.ts:235) always populates groupId from a valid group object. However, the bulk import path (complete-program-applications.ts:91) uses optional chaining (programApplication?.partnerGroup?.id), allowing null values to be persisted.

Since the learning confirms that default partner groups always exist when programs are created, verify:

  1. Whether the bulk import path should also guarantee a groupId (e.g., by falling back to default group)
  2. If yes, update the schema to z.string() (required) and fix complete-program-applications.ts
  3. If no, restore the TODO comment explaining why null is valid
apps/web/ui/webhooks/add-edit-webhook-form.tsx (2)

124-136: Verify program is loaded before filtering program-level triggers.

When defaultProgramId exists but program is still loading, program?.payoutMode evaluates to undefined. The filter condition program?.payoutMode !== "internal" then evaluates to true, causing payout.confirmed to be included even when the program's payoutMode might actually be "internal" once loaded.

Consider adding a check for program loading state:

 const availableWebhookTriggers = useMemo(
   () => [
     ...WORKSPACE_LEVEL_WEBHOOK_TRIGGERS,
     ...(defaultProgramId
       ? PROGRAM_LEVEL_WEBHOOK_TRIGGERS.filter(
           (trigger) =>
             trigger !== "payout.confirmed" ||
-            program?.payoutMode !== "internal",
+            (program && program.payoutMode !== "internal"),
         )
       : []),
   ],
-  [defaultProgramId, program?.payoutMode],
+  [defaultProgramId, program],
 );

This ensures that if program is undefined, the condition evaluates to false and payout.confirmed is excluded until the program data is available.


216-216: LGTM!

The updated rendering logic correctly uses availableWebhookTriggers to display both workspace-level and program-level triggers when a program is available.

apps/web/lib/webhook/types.ts (1)

7-7: LGTM!

The new PartnerApplicationWebhookPayload type is correctly defined and properly integrated into the WebhookEventPayload union. The implementation follows the established pattern for other webhook payload types.

Also applies to: 22-24, 42-42

apps/web/lib/webhook/schemas.ts (1)

10-10: LGTM!

The new partner.application_submitted webhook event variant is well-structured and follows the established pattern for other webhook events in this file. The OpenAPI metadata is properly configured with appropriate ref and description.

Also applies to: 123-134

apps/web/lib/partners/complete-program-applications.ts (4)

104-121: LGTM!

The workspace fetching and mapping logic is well-implemented. Using a Map for quick lookup by defaultProgramId is efficient, and the subsequent conditional check ensures webhooks are only sent for programs that have associated workspaces.


132-160: LGTM!

The logic for computing missing social fields is correct. It properly identifies fields present in the application but absent in the partner profile, and the hasMissingSocialFields check efficiently determines whether any update is needed.


162-167: LGTM!

The applicationFormData formatting is well-structured. Using the helper function and converting empty strings to null provides a clean, consistent webhook payload format.


196-213: Webhook emission logic is well-structured, but note the status override.

The conditional webhook sending and payload construction are well-implemented. The check on line 197 ensures webhooks are only sent when a workspace exists.

Note that line 208 explicitly sets status: "pending", which overrides any status from programEnrollment. This appears intentional but ensure it aligns with the expected webhook payload for newly submitted applications.

apps/web/lib/webhook/constants.ts (2)

17-24: LGTM!

The introduction of PROGRAM_LEVEL_WEBHOOK_TRIGGERS is well-structured and properly integrated into the WEBHOOK_TRIGGERS array. The categorization clearly separates workspace-level, program-level, and link-level triggers.

Also applies to: 30-30


34-50: LGTM!

The typed WEBHOOK_TRIGGER_DESCRIPTIONS as a Record provides excellent type safety, ensuring all triggers have corresponding descriptions. The description for "partner.application_submitted" is clear and consistent with other trigger descriptions.

apps/web/lib/partners/format-application-form-data.ts (2)

4-11: LGTM! Good refactor to use a named interface.

The introduction of FormDataKeyValue improves type reusability and makes the return type clearer. The filter and cast on line 52 correctly removes the null values returned for "website-and-socials" field types.


55-72: LGTM! Clean extraction and normalization.

The new function correctly extracts website and social fields from the application form data and appropriately normalizes empty strings to null for downstream webhook payloads.

apps/web/lib/actions/partners/create-program-application.ts (5)

8-18: LGTM! Clean integration of webhook dependencies.

The new imports for formatting utilities and webhook publishing are appropriate, and the workspace selection correctly fetches only the fields needed for webhook dispatch (id and webhookEnabled).

Also applies to: 136-141


172-177: LGTM! Workspace threading is clean and type-safe.

The workspace parameter is correctly passed through to createApplicationAndEnrollment, and the use of Pick<Project, "id" | "webhookEnabled"> provides precise typing without exposing unnecessary fields.

Also applies to: 197-209


218-242: LGTM! Correctly captures programEnrollment for webhook payload.

Good change from discarding the enrollment result (_) to capturing it as programEnrollment, which is needed for constructing the webhook payload later.


246-251: LGTM! Data transformation is correct.

The mapping from title to label aligns with the webhook schema requirements, and empty string normalization to null is appropriate for the webhook payload.


272-287: The webhook payload structure is correct and safe.

Verification confirms the spread operator field merge order produces the expected payload structure. The partnerApplicationWebhookSchema uses .pick() to select only specific fields from each schema:

  • EnrolledPartnerSchema.pick({id, name, companyName, email, image, description, country})
  • ProgramEnrollmentSchema.pick({groupId, status})
  • PartnerOnlinePresenceSchema.pick({website, youtube, twitter, linkedin, instagram, tiktok})

These picked fields are all distinct—there are no overlaps. The schema validation via .parse() enforces this subset constraint, ensuring the final webhook payload contains only the expected fields regardless of spread order. The formatWebsiteAndSocialsFields() function correctly returns fields matching the picked online presence schema fields.

The explicit id: partner.id assignment safely ensures the partner's ID is used, and any field overwrites from the spreads are intentional (e.g., form data overrides). The code is correct as-is.


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.

@devkiran devkiran marked this pull request as ready for review November 5, 2025 17:13
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: 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/partners/complete-program-applications.ts (1)

120-207: Ensure webhook payload includes the freshly created enrollment data

partner.programs is the snapshot loaded before we call programEnrollment.createMany, so programEnrollment is undefined for every newly completed application. As soon as we hit partnerApplicationWebhookSchema.parse(...), the required status field is missing and Zod throws synchronously, which also prevents Promise.allSettled from running and halts the rest of the completion flow. This means applications that should succeed will instead log an error and skip the webhook, auto-approve job, etc.

Please hydrate the enrollment data from the rows we just inserted (or otherwise supply the required fields) before building the webhook payload. One possible fix:

@@
     await prisma.programEnrollment.createMany({
       data: programEnrollments,
       skipDuplicates: true,
     });
 
+    const programEnrollmentsForWebhook = await prisma.programEnrollment.findMany({
+      where: {
+        partnerId: user.partners[0].partnerId,
+        programId: {
+          in: filteredProgramApplications.map((p) => p.programId),
+        },
+      },
+      select: {
+        programId: true,
+        groupId: true,
+        tenantId: true,
+        status: true,
+      },
+    });
+
+    const programEnrollmentsByProgramId = new Map(
+      programEnrollmentsForWebhook.map((enrollment) => [
+        enrollment.programId,
+        enrollment,
+      ]),
+    );
@@
-      const programEnrollment = partner.programs.find(
-        (p) => p.programId === programApplication.programId,
-      );
+      const programEnrollment = programEnrollmentsByProgramId.get(
+        programApplication.programId,
+      );
@@
-        workspacesByProgramId.has(program.id) &&
+        workspacesByProgramId.has(program.id) &&
+          programEnrollment &&
           sendWorkspaceWebhook({
             workspace: workspacesByProgramId.get(program.id)!,
             trigger: "partner.application_submitted",
             data: partnerApplicationWebhookSchema.parse({
               id: application.id,
               partner: {
                 ...partner,
                 ...programEnrollment,
               },
               applicationForm,
             }),
           }),
♻️ Duplicate comments (1)
apps/web/lib/integrations/common/ui/configure-webhook.tsx (1)

29-29: Verify defaultProgramId is returned by useWorkspace.

Same concern as in add-edit-webhook-form.tsx - ensure that defaultProgramId is actually returned by the useWorkspace hook.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b3b6de and ab32803.

📒 Files selected for processing (13)
  • apps/web/lib/actions/partners/create-program-application.ts (8 hunks)
  • apps/web/lib/integrations/common/ui/configure-webhook.tsx (4 hunks)
  • apps/web/lib/integrations/slack/transform.ts (3 hunks)
  • apps/web/lib/partners/complete-program-applications.ts (4 hunks)
  • apps/web/lib/webhook/constants.ts (2 hunks)
  • apps/web/lib/webhook/sample-events/partner-application-submitted.json (1 hunks)
  • apps/web/lib/webhook/sample-events/payload.ts (2 hunks)
  • apps/web/lib/webhook/schemas.ts (2 hunks)
  • apps/web/lib/webhook/types.ts (3 hunks)
  • apps/web/lib/zod/schemas/program-application.ts (1 hunks)
  • apps/web/lib/zod/schemas/programs.ts (1 hunks)
  • apps/web/tests/webhooks/index.test.ts (2 hunks)
  • apps/web/ui/webhooks/add-edit-webhook-form.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/lib/zod/schemas/programs.ts
📚 Learning: 2025-08-14T05:00:23.224Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/app/api/og/program/route.tsx:63-64
Timestamp: 2025-08-14T05:00:23.224Z
Learning: In Dub's partner program system, the default partner group will always exist. When programs are created, a default partner group is automatically upserted using DEFAULT_PARTNER_GROUP constant, so accessing program.groups[0] in contexts where the default group is queried is safe.

Applied to files:

  • apps/web/lib/zod/schemas/programs.ts
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.

Applied to files:

  • apps/web/tests/webhooks/index.test.ts
  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/actions/partners/create-program-application.ts
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/ui/webhooks/add-edit-webhook-form.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/partners/complete-program-applications.ts
  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/actions/partners/create-program-application.ts
  • apps/web/lib/webhook/constants.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/webhook/sample-events/payload.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/zod/schemas/program-application.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/integrations/slack/transform.ts
  • apps/web/lib/webhook/constants.ts
📚 Learning: 2025-08-25T17:33:45.072Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts:12-12
Timestamp: 2025-08-25T17:33:45.072Z
Learning: The WorkflowTrigger enum in packages/prisma/schema/workflow.prisma contains three values: leadRecorded, saleRecorded, and commissionEarned. All three are properly used throughout the codebase.

Applied to files:

  • apps/web/lib/webhook/constants.ts
🧬 Code graph analysis (9)
apps/web/lib/webhook/schemas.ts (1)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-39)
apps/web/tests/webhooks/index.test.ts (1)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-39)
apps/web/ui/webhooks/add-edit-webhook-form.tsx (2)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
apps/web/lib/webhook/constants.ts (2)
  • WORKSPACE_LEVEL_WEBHOOK_TRIGGERS (9-15)
  • PROGRAM_LEVEL_WEBHOOK_TRIGGERS (17-23)
apps/web/lib/partners/complete-program-applications.ts (3)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/partners/format-application-form-data.ts (1)
  • formatApplicationFormData (4-48)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-39)
apps/web/lib/zod/schemas/program-application.ts (2)
apps/web/lib/zod/schemas/partners.ts (2)
  • EnrolledPartnerSchema (333-398)
  • PartnerOnlinePresenceSchema (193-227)
apps/web/lib/zod/schemas/programs.ts (1)
  • ProgramEnrollmentSchema (84-133)
apps/web/lib/integrations/slack/transform.ts (1)
apps/web/lib/webhook/types.ts (1)
  • PartnerApplicationWebhookPayload (21-23)
apps/web/lib/webhook/types.ts (1)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-39)
apps/web/lib/integrations/common/ui/configure-webhook.tsx (2)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
apps/web/lib/webhook/constants.ts (3)
  • WORKSPACE_LEVEL_WEBHOOK_TRIGGERS (9-15)
  • PROGRAM_LEVEL_WEBHOOK_TRIGGERS (17-23)
  • WEBHOOK_TRIGGER_DESCRIPTIONS (33-48)
apps/web/lib/actions/partners/create-program-application.ts (3)
apps/web/lib/zod/schemas/programs.ts (1)
  • createProgramApplicationSchema (164-171)
apps/web/lib/partners/format-application-form-data.ts (1)
  • formatApplicationFormData (4-48)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-39)
🔇 Additional comments (9)
apps/web/lib/zod/schemas/programs.ts (1)

86-86: LGTM - TODO cleanup.

Removing the TODO comment is appropriate since this PR introduces the program application webhook schema that likely addresses the concern.

apps/web/tests/webhooks/index.test.ts (1)

16-16: LGTM - Test coverage extended.

The new webhook event is properly registered in the test suite, consistent with the existing pattern for other webhook events.

Also applies to: 71-71

apps/web/lib/webhook/sample-events/payload.ts (1)

10-10: LGTM - Sample payload wired correctly.

The new event payload is properly integrated into the sample events mapping. However, ensure the sample JSON structure in partner-application-submitted.json is corrected to match the schema definition.

Also applies to: 21-21

apps/web/lib/webhook/types.ts (1)

6-6: LGTM - Type definition follows established pattern.

The new webhook payload type is properly defined and integrated into the type system, consistent with existing webhook event types.

Also applies to: 21-23, 37-37

apps/web/ui/webhooks/add-edit-webhook-form.tsx (2)

122-125: Conditional program-level triggers logic looks correct.

The logic appropriately shows program-level webhook triggers only when a default program exists (defaultProgramId is truthy), which prevents displaying irrelevant options when no program context is available.


37-42: No issues found. The review comment is incorrect.

The useWorkspace() hook does return defaultProgramId. The hook spreads the entire workspace object (typed as ExtendedWorkspaceProps), which explicitly defines defaultProgramId: string | null. This field comes from the API response and is correctly destructured in the webhook form at line 41. The conditional usage at line 124 is also valid.

apps/web/lib/integrations/common/ui/configure-webhook.tsx (1)

94-97: Conditional trigger display with proper filtering.

The implementation correctly combines program-level and workspace-level triggers when a program context exists, with appropriate filtering by permissions (canManageWebhook) and integration capabilities (supportedEvents).

Also applies to: 118-158

apps/web/lib/integrations/slack/transform.ts (2)

254-307: LGTM - Slack template follows established patterns.

The template structure is consistent with existing webhook templates, properly formats the partner application data, and includes helpful context (Application ID and view link). The conditional rendering of companyName is a nice touch.


482-482: Template properly registered.

The new template is correctly wired into the slackTemplates map for the partner.application_submitted event.

…fields, update application form data structure, and enhance webhook payload schema for partner application submissions.
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: 1

🧹 Nitpick comments (1)
apps/web/lib/partners/format-application-form-data.ts (1)

55-72: Review the empty string handling logic.

On Line 66, the ternary expression item.value !== "" ? item.value : "" will always return item.value (even if it's an empty string). This seems inconsistent with the return type Record<string, string | null> which suggests null could be a value.

Consider whether empty strings should be converted to null for consistency:

-        result[item.type] = item.value !== "" ? item.value : "";
+        result[item.type] = item.value !== "" ? item.value : null;

Or if empty strings are intentional, update the return type to Record<string, string>.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab32803 and fbaaa4b.

📒 Files selected for processing (6)
  • apps/web/lib/actions/partners/create-program-application.ts (8 hunks)
  • apps/web/lib/partners/complete-program-applications.ts (4 hunks)
  • apps/web/lib/partners/format-application-form-data.ts (2 hunks)
  • apps/web/lib/webhook/sample-events/partner-application-submitted.json (1 hunks)
  • apps/web/lib/webhook/sample-events/partner-enrolled.json (2 hunks)
  • apps/web/lib/zod/schemas/program-application.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/webhook/sample-events/partner-application-submitted.json
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/partners/complete-program-applications.ts
  • apps/web/lib/actions/partners/create-program-application.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/zod/schemas/program-application.ts
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.

Applied to files:

  • apps/web/lib/zod/schemas/program-application.ts
  • apps/web/lib/actions/partners/create-program-application.ts
🧬 Code graph analysis (4)
apps/web/lib/zod/schemas/program-application.ts (2)
apps/web/lib/zod/schemas/partners.ts (2)
  • EnrolledPartnerSchema (333-398)
  • PartnerOnlinePresenceSchema (193-227)
apps/web/lib/zod/schemas/programs.ts (1)
  • ProgramEnrollmentSchema (84-133)
apps/web/lib/partners/format-application-form-data.ts (1)
apps/web/lib/types.ts (1)
  • ProgramApplicationFormDataWithValues (476-478)
apps/web/lib/partners/complete-program-applications.ts (2)
apps/web/lib/partners/format-application-form-data.ts (2)
  • formatApplicationFormData (9-53)
  • formatWebsiteAndSocialsFields (55-72)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-42)
apps/web/lib/actions/partners/create-program-application.ts (3)
apps/web/lib/zod/schemas/programs.ts (1)
  • createProgramApplicationSchema (164-171)
apps/web/lib/partners/format-application-form-data.ts (2)
  • formatApplicationFormData (9-53)
  • formatWebsiteAndSocialsFields (55-72)
apps/web/lib/zod/schemas/program-application.ts (1)
  • partnerApplicationWebhookSchema (5-42)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (16)
apps/web/lib/webhook/sample-events/partner-enrolled.json (1)

2-2: LGTM! Sample data ID format updated consistently.

The ID values have been updated to use a consistent format across both the top-level id and partnerId fields.

Also applies to: 11-11

apps/web/lib/zod/schemas/program-application.ts (1)

1-42: LGTM! Well-structured webhook schema.

The schema correctly imports from the standard zod package and combines multiple schemas to create a comprehensive webhook payload structure. The applicationFormData array structure with label/value pairs is well-suited for webhook payloads.

apps/web/lib/partners/complete-program-applications.ts (5)

7-12: LGTM! Necessary imports and data selection for webhook integration.

The new imports support webhook publishing and application data formatting. Extending the program selection to include tenantId, status, and groupId is appropriate for constructing the webhook payload.

Also applies to: 30-34


104-121: LGTM! Efficient workspace fetching and mapping.

The implementation efficiently fetches all necessary workspace data in a single query and creates a Map for O(1) lookups during webhook emission. This is a good pattern for batch processing.


131-160: LGTM! More readable social fields mapping.

The explicit multi-line object literal improves readability while maintaining the same functionality. Each social field is clearly mapped with its conditional logic.


162-167: LGTM! Application form data properly formatted for webhook.

The application form data is correctly transformed from the internal format to a label/value structure suitable for webhook payloads, with empty strings converted to null.


196-212: LGTM! Webhook emission with proper validation and conditional execution.

The webhook is conditionally sent only when a workspace exists, and the payload is validated against the schema before emission. This is good defensive programming. Note: ensure programEnrollment (Line 206) is always defined as flagged in a separate comment.

apps/web/lib/partners/format-application-form-data.ts (2)

4-7: LGTM! Good practice to define a shared interface.

The FormDataKeyValue interface improves type safety and makes the return type explicit and reusable across the codebase.


9-53: LGTM! Consistent use of the new interface.

The return type and type assertion have been updated to use the new FormDataKeyValue interface, improving type safety without changing functionality.

apps/web/lib/actions/partners/create-program-application.ts (7)

8-11: LGTM! Necessary imports for webhook integration.

All new imports support the webhook functionality: formatting utilities, webhook publishing, schema validation, and type definitions.

Also applies to: 17-18, 27-27


136-141: LGTM! Workspace data included for webhook emission.

The program query now includes the workspace data necessary for webhook publishing, selecting only the required fields.


171-177: LGTM! Function call updated with workspace parameter.

The function call correctly passes the workspace data to createApplicationAndEnrollment.


197-209: LGTM! Function signature properly updated with new parameters.

The function signature correctly includes the workspace parameter and updates the partner type to include the programs array, which is necessary for the enrollment check and webhook payload construction.


218-242: LGTM! Promise.all updated to include program enrollment.

The Promise.all correctly creates both the application and program enrollment records, with proper destructuring to capture both results.


246-251: LGTM! Application form data properly formatted.

The application form data is correctly transformed to the label/value structure needed for webhook payloads, with empty strings converted to null.


273-287: LGTM! Webhook emission with proper validation.

The webhook payload is correctly constructed and validated against the schema before emission. The workspace is guaranteed to exist from the program query (Lines 136-141), so no additional conditional check is needed here.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

✅ Actions performed

Full review triggered.

@steven-tey steven-tey merged commit 9147237 into main Nov 6, 2025
8 of 9 checks passed
@steven-tey steven-tey deleted the application-submitted-webhook-event branch November 6, 2025 23:42
@coderabbitai coderabbitai bot mentioned this pull request Nov 24, 2025
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.

3 participants