Skip to content

feat: handle reservation expiry in webhooks #22724

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

giripatel
Copy link
Contributor

What does this PR do?

This feature introduces a new webhook event that triggers when a reservation expires. It allows external systems to receive real-time notifications about expired reservations, enabling better automation and integration workflows.

Key Highlights:

  • Event Trigger: Fires automatically upon reservation expiration.

  • Payload: Includes reservation details such as id, timestamps, and status.

  • Integration: Can be configured via existing webhook settings.

  • Fixes feature: expired reservation tracking #21845

  • Fixes CAL-5934

Visual Demo

Video Demo:

https://www.loom.com/share/af4a4c676c12435d96369c35c4815860?sid=5c595cc2-0752-417f-aa57-406f7ae24a72

Image Demo of Final Payload:

Screenshot from 2025-06-24 04-27-18

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Add or configure a webhook URL in the system to listen for "Reservation Expired" events.
  2. Create a reservation with a known expiration time.
  3. Allow the reservation to expire naturally (do not manually cancel it).
  4. Ensure that the system processes the slot release.
  5. The event type is correct: "RESERVATION_EXPIRED".
  6. The payload includes accurate and complete reservation data.
  • Are there environment variables that should be set?

No

  • What are the minimal test data to have?

Available Time slot

  • What is expected (happy path) to have (input and output)?

Configured Event trigger with reservation slot data.


Summary by cubic

Added a new webhook event that triggers when a reservation expires, allowing external systems to get notified in real time.

  • New Features
    • Sends a RESERVATION_EXPIRED webhook with reservation details when a slot expires.
    • Updated docs, translations, and webhook configuration to support the new event.

@giripatel giripatel requested review from a team as code owners July 24, 2025 21:20
Copy link

vercel bot commented Jul 24, 2025

@giripatel is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added api area: API, enterprise API, access token, OAuth platform Anything related to our platform plan ✨ feature New feature or request labels Jul 24, 2025
Copy link
Contributor

coderabbitai bot commented Jul 24, 2025

"""

Walkthrough

This change introduces the "reservation expired" webhook event throughout the application. It updates the database schema and Prisma enum to include RESERVATION_EXPIRED as a valid webhook trigger event. The webhook constants and form components are modified to recognize and allow configuration for this new event. A new scheduling function, scheduleReservationExpiredTrigger, is implemented to create or update scheduled webhooks when a reservation is made. The slot reservation handler is updated to invoke this scheduling logic after a reservation is created. Localization files for all supported languages receive a new translation string for "reservation expired." Documentation is updated to reflect the new webhook trigger event.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

  • Complexity: Moderate. The changes span database schema, backend logic, webhook scheduling, localization, and documentation. The core logic for scheduling reservation expiration webhooks and integrating it into the reservation flow requires careful review, but most other changes are straightforward additions (enums, constants, translations).
  • Volume: Multiple files across backend, frontend, database, and localization.
  • Estimated time: Reviewing the new scheduling logic and its integration (~10 minutes), plus spot-checking the enum, constants, and translation additions (~8 minutes).
    """

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 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 a74f63a and 750bf4a.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.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). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the ❗️ migrations contains migration files label Jul 24, 2025
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jul 24, 2025
@graphite-app graphite-app bot requested review from a team July 24, 2025 21:20
@dosubot dosubot bot added i18n area: i18n, translations webhooks area: webhooks, callback, webhook payload labels Jul 24, 2025
Copy link

graphite-app bot commented Jul 24, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (07/24/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (07/24/25)

1 label was added to this PR based on Keith Williams's automation.

"Add platform team as reviewer" took an action on this PR • (07/24/25)

1 reviewer was added to this PR based on Keith Williams's automation.

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: 3

🧹 Nitpick comments (5)
apps/web/public/static/locales/ro/common.json (1)

3362-3362: Prefer past-tense wording for the “expired” state

Using the present tense (“Rezervarea expiră” – “is expiring”) can be confused with a future/ongoing action.
The event represents a reservation that has already expired, so past tense is clearer:

-"reservation_expired": "Rezervarea expiră"
+"reservation_expired": "Rezervarea a expirat"
apps/web/public/static/locales/hu/common.json (1)

3362-3362: Verify translation consistency for new key reservation_expired.

The added Hungarian text “A foglalás lejárt” is grammatically correct, but for consistency with other past-tense notification strings ("booking_cancelled": "Foglalás lemondva", "meeting_ended": "A találkozó véget ért"), you may want to use the same impersonal style:

-"reservation_expired": "A foglalás lejárt",
+"reservation_expired": "Foglalás lejárt",

Not blocking, but worth aligning wording across similar status messages.

apps/web/public/static/locales/sk/common.json (1)

69-70: Comma fix is correct; consider alphabetical placement for consistency

The added comma restores valid JSON, and the Slovak translation looks accurate.
To maintain readability in large locale files, keeping keys in alphabetical (or existing logical) order helps future diff navigation—e.g., placing email_survey_triggered_by_workflow alongside the other email_* entries.

apps/web/public/static/locales/id/common.json (1)

168-169: Consider using the standard spelling “kedaluwarsa”.

KBBI lists “kedaluwarsa” as the preferred Indonesian spelling; “kadaluarsa” is informal. Updating the value keeps terminology consistent with other formal strings in this file.

-  "reservation_expired": "Reservasi kadaluarsa"
+  "reservation_expired": "Reservasi kedaluwarsa"
packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts (1)

103-147: Consider performance optimization and improve error handling.

The implementation correctly schedules webhook triggers for reservation expiration, but there are some areas for improvement:

  1. Performance: Using await inside map() creates sequential execution. For team events with many users, this could be slow.
  2. Error handling: The catch block on line 149 throws a generic error that doesn't reflect the actual failure reason.

Consider this optimization:

-        eventType.users.map(async (user) => {
+        eventType.users.map((user) => {
           const slot = await prisma.selectedSlots.upsert({
             // ... existing upsert logic
           });

           const orgId = await getOrgIdFromMemberOrTeamId({ memberId: user.id, teamId });

           const subscriberOptionsReservations = {
             userId: user.id,
             eventTypeId,
             triggerEvent: WebhookTriggerEvents.RESERVATION_EXPIRED,
             teamId,
             orgId,
           };

           const webhooks = await getWebhooks(subscriberOptionsReservations);

-          for (const subscriber of webhooks) {
-            scheduleReservationExpiredTrigger({
-              slot: slot,
-              subscriberUrl: subscriber.subscriberUrl,
-              subscriber,
-              triggerEvent: WebhookTriggerEvents.RESERVATION_EXPIRED,
-            });
-          }
+          return Promise.all(
+            webhooks.map((subscriber) =>
+              scheduleReservationExpiredTrigger({
+                slot: slot,
+                subscriberUrl: subscriber.subscriberUrl,
+                subscriber,
+                triggerEvent: WebhookTriggerEvents.RESERVATION_EXPIRED,
+              })
+            )
+          );
         })

Also, improve error handling to preserve the original error:

    } catch (error) {
      throw new TRPCError({
-        message: "Event type not found",
+        message: error instanceof Error ? error.message : "Failed to reserve slot",
        code: "NOT_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 f991669 and 6b869a5.

📒 Files selected for processing (48)
  • apps/web/public/static/locales/ar/common.json (1 hunks)
  • apps/web/public/static/locales/az/common.json (1 hunks)
  • apps/web/public/static/locales/bg/common.json (1 hunks)
  • apps/web/public/static/locales/ca/common.json (1 hunks)
  • apps/web/public/static/locales/cs/common.json (1 hunks)
  • apps/web/public/static/locales/da/common.json (1 hunks)
  • apps/web/public/static/locales/de/common.json (1 hunks)
  • apps/web/public/static/locales/el/common.json (1 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • apps/web/public/static/locales/es-419/common.json (1 hunks)
  • apps/web/public/static/locales/es/common.json (1 hunks)
  • apps/web/public/static/locales/et/common.json (1 hunks)
  • apps/web/public/static/locales/eu/common.json (1 hunks)
  • apps/web/public/static/locales/fi/common.json (1 hunks)
  • apps/web/public/static/locales/fr/common.json (1 hunks)
  • apps/web/public/static/locales/he/common.json (1 hunks)
  • apps/web/public/static/locales/hr/common.json (1 hunks)
  • apps/web/public/static/locales/hu/common.json (1 hunks)
  • apps/web/public/static/locales/id/common.json (1 hunks)
  • apps/web/public/static/locales/it/common.json (1 hunks)
  • apps/web/public/static/locales/ja/common.json (1 hunks)
  • apps/web/public/static/locales/km/common.json (1 hunks)
  • apps/web/public/static/locales/ko/common.json (1 hunks)
  • apps/web/public/static/locales/lv/common.json (1 hunks)
  • apps/web/public/static/locales/nl/common.json (1 hunks)
  • apps/web/public/static/locales/no/common.json (1 hunks)
  • apps/web/public/static/locales/pl/common.json (1 hunks)
  • apps/web/public/static/locales/pt-BR/common.json (1 hunks)
  • apps/web/public/static/locales/pt/common.json (1 hunks)
  • apps/web/public/static/locales/ro/common.json (1 hunks)
  • apps/web/public/static/locales/ru/common.json (1 hunks)
  • apps/web/public/static/locales/sk-SK/common.json (1 hunks)
  • apps/web/public/static/locales/sk/common.json (1 hunks)
  • apps/web/public/static/locales/sr/common.json (1 hunks)
  • apps/web/public/static/locales/sv/common.json (1 hunks)
  • apps/web/public/static/locales/ta/common.json (1 hunks)
  • apps/web/public/static/locales/tr/common.json (1 hunks)
  • apps/web/public/static/locales/uk/common.json (1 hunks)
  • apps/web/public/static/locales/vi/common.json (1 hunks)
  • apps/web/public/static/locales/zh-CN/common.json (1 hunks)
  • apps/web/public/static/locales/zh-TW/common.json (1 hunks)
  • docs/developing/guides/automation/webhooks.mdx (1 hunks)
  • packages/features/webhooks/components/WebhookForm.tsx (1 hunks)
  • packages/features/webhooks/lib/constants.ts (1 hunks)
  • packages/features/webhooks/lib/scheduleTrigger.ts (2 hunks)
  • packages/prisma/migrations/20250724184730_add_reservation_expired_webhook_enum/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/features/webhooks/lib/constants.ts
  • packages/features/webhooks/components/WebhookForm.tsx
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts
  • packages/features/webhooks/lib/scheduleTrigger.ts
🧠 Learnings (23)
apps/web/public/static/locales/en/common.json (2)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to docs/api-reference/v2/openapi.json : When docs changes are made in /docs/api-reference/v2/openapi.json, ensure the following: 'summary' fields are written in proper American english.

packages/features/webhooks/lib/constants.ts (1)

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

apps/web/public/static/locales/az/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/sr/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/zh-TW/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/sk/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/zh-CN/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/id/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/es-419/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/hr/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/lv/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/pt-BR/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/it/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/ca/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/sk-SK/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/cs/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/bg/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts (3)

Learnt from: vijayraghav-io
PR: #21072
File: packages/app-store/office365calendar/api/webhook.ts:120-123
Timestamp: 2025-07-18T17:57:16.395Z
Learning: The office365calendar webhook handler in packages/app-store/office365calendar/api/webhook.ts is specifically designed for Office365 calendar integration, not as a generic webhook handler. Therefore, it's safe to assume that fetchAvailabilityAndSetCache method will be implemented in the Office365CalendarService, making explicit validation checks unnecessary.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: The Outlook Calendar integration in Cal.com intentionally reuses subscription IDs across multiple event types for efficiency. The upsertSelectedCalendarsForEventTypeIds method creates separate SelectedCalendar records for each eventTypeId, all sharing the same outlookSubscriptionId. This subscription sharing pattern means that unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be applied as they would prevent this intended functionality.

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be added as they would break this subscription sharing functionality.

apps/web/public/static/locales/ru/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/sv/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/ro/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/public/static/locales/es/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

packages/features/webhooks/lib/scheduleTrigger.ts (1)

Learnt from: vijayraghav-io
PR: #21072
File: packages/prisma/schema.prisma:891-891
Timestamp: 2025-07-18T08:47:01.264Z
Learning: In Cal.com's calendar integration, both Google Calendar and Outlook Calendar are designed to allow multiple eventTypeIds to share the same subscription ID (googleChannelId or outlookSubscriptionId). This is an intentional design pattern to reuse existing subscriptions for efficiency rather than creating separate subscriptions for each event type. Therefore, unique constraints like @@unique([outlookSubscriptionId, eventTypeId]) should not be added as they would break this subscription sharing functionality.

⏰ 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: Detect changes
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (44)
apps/web/public/static/locales/ja/common.json (1)

3362-3362: Translation looks good – no action needed

The newly-added key reservation_expired correctly maps to the Japanese translation “予約期限切れ”. No issues found.

apps/web/public/static/locales/az/common.json (1)

3362-3363: New key correctly added above the sentinel marker

The "reservation_expired" translation is inserted just before the ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS guard, keeping the expected order and preserving merge-safety. No JSON syntax issues spotted.

packages/prisma/schema.prisma (2)

968-988: Added new enum value RESERVATION_EXPIRED – looks correct and consistent

The new value is alphabetically positioned after FORM_SUBMITTED_NO_EVENT, the naming follows the established screaming-snake convention, and you already wired a migration elsewhere. No objections here.


968-988: Verify new enum coverage across all conditional logic
Automated searches didn’t uncover any switch(WebhookTriggerEvents…) statements, and most trigger‐handling is implemented via if/else checks and array literals. Please manually confirm that every codepath comparing or listing WebhookTriggerEvents now includes all new values (e.g. FORM_SUBMITTED_NO_EVENT, RECORDING_TRANSCRIPTION_GENERATED, INSTANT_MEETING, OOO_CREATED, AFTER_HOSTS_CAL_VIDEO_NO_SHOW, AFTER_GUESTS_CAL_VIDEO_NO_SHOW, RESERVATION_EXPIRED). Key areas to audit:

  • Webhook scheduling library: packages/features/webhooks/lib/scheduleTrigger.ts
  • UI components and forms: WebhookForm.tsx, WebhookListItem.tsx, EventTypeWebhookListItem.tsx
  • TRPC handlers issuing webhooks: outOfOfficeCreateOrUpdate.handler.ts, reserveSlot.handler.ts, booking/payment handlers
  • Zod schemas and API inputs/outputs: packages/prisma/zod/webhook.ts, apps/api/v2/src/modules/webhooks/*
  • Daily and Zapier webhook runners: apps/web/lib/daily-webhook/triggerWebhooks.ts, packages/app-store/zapier/...
  • Tests asserting specific events: any *.test.ts files under packages/features/tasker and packages/features/webhooks

Ensure no WebhookTriggerEvents comparisons or arrays omit the newly added enum members.

apps/web/public/static/locales/zh-TW/common.json (1)

3362-3362: Localization string added correctly

The added Traditional Chinese translation (“預訂已過期”) accurately reflects the English source “Reservation expired,” and no interpolation variables are involved, so nothing else is required.

apps/web/public/static/locales/zh-CN/common.json (1)

3362-3362: New translation entry looks good

The added key/value pair accurately translates “reservation expired” to “预订已过期” and follows existing locale-file conventions. No further action needed.

apps/web/public/static/locales/vi/common.json (1)

3360-3364: Translation key added correctly

The new key reservation_expired is placed above the merge-conflict sentinel and includes a proper Vietnamese translation (“Đặt chỗ đã hết hạn”). Value grammar and punctuation look good, and the trailing comma preserves valid JSON. No further action needed.

apps/web/public/static/locales/km/common.json (1)

3362-3362: Consider alphabetic placement and plural-form consistency for the new key.

  1. All existing keys are kept in lexical order.
    Placing "reservation_expired" after "reservation_expired"’s neighbours keeps diff-noise low and helps future merges.
    Currently it sits between two large “ADD_NEW_STRINGS…” guards, so no breakage – just double-check there isn’t another copy earlier in the file.

  2. If other locales already define reservation_expired_one / _other (pluralisation), add the Khmer one too; i18next will otherwise fall back to English.

No functional issue – more a housekeeping note.

apps/web/public/static/locales/et/common.json (1)

3362-3363: Localization key added correctly – double-check parity across locales

"reservation_expired": "Broneering aegunud" looks good (grammar & diacritics are fine) and the trailing comma is preserved for the next entry.
Please ensure the same key exists in the base/en locale and all other languages so runtime look-ups never fall through.

apps/web/public/static/locales/es/common.json (1)

3362-3363: Locale key added correctly
The new entry "reservation_expired": "Reserva vencida" follows the existing pattern, is placed above the sentinel key, and keeps the JSON valid. No further action needed.

apps/web/public/static/locales/pl/common.json (1)

3362-3362: Translation entry looks good – no issues spotted

The new key/value pair follows the existing pattern (snake-case English key mapped to a concise Polish translation) and the trailing comma is present, so the JSON remains valid.
No further action required.

apps/web/public/static/locales/pt-BR/common.json (1)

3362-3362: Translation entry looks good and JSON remains valid

“Reserva expirada” is a faithful translation of “Reservation Expired” and the trailing comma keeps the file syntactically correct.
No further changes needed here.

apps/web/public/static/locales/ar/common.json (1)

3362-3362: Looks good – key and translation added correctly

The new "reservation_expired" key is in the expected camel-case format and the Arabic translation “انتهت صلاحية الحجز” is accurate and consistent with existing entries. No further action needed.

apps/web/public/static/locales/ko/common.json (1)

3362-3362: Translation looks good – just check cross-locale consistency.

"reservation_expired": "예약 만료" accurately reflects “reservation expired.”
Nothing else to flag here, provided the same key exists in the other locale files so UI fallback logic stays intact.

apps/web/public/static/locales/en/common.json (1)

3390-3390: String key added as expected – ensure other locales stay in sync

The new "reservation_expired" entry follows the existing naming/style conventions and is correctly placed before the merge-conflict sentinel.
Just double-check that:

  1. All other locale JSONs include the same key (already mentioned in the PR description).
  2. Any UI that consumes the new key has a sensible fallback to avoid “missing translation” glitches while deployments roll out.

Otherwise, looks good.

apps/web/public/static/locales/pt/common.json (1)

3361-3363: String key added correctly – matches new event enum

The new key "reservation_expired" is added with a concise, idiomatic PT-BR translation and alphabetic ordering is preserved. 👍
No further action needed.

apps/web/public/static/locales/tr/common.json (1)

3362-3362: New key looks good.

The "reservation_expired": "Rezervasyon süresi doldu" entry is accurate, consistent with existing phrasing, and does not conflict with any prior key.
No further action needed.

apps/web/public/static/locales/sv/common.json (1)

3362-3362: Swedish phrasing feels off – suggest a more natural translation.

Current:

"reservation_expired": "Bokning utgången"

A Swedish speaker would normally say “Bokningen har löpt ut” or “Bokningen har gått ut”.
Suggest replacing the value:

-"reservation_expired": "Bokning utgången"
+"reservation_expired": "Bokningen har löpt ut"

This keeps tense/definiteness consistent with neighbouring strings like “Mötet är schemalagt” etc.
[ suggest_nitpick ]

apps/web/public/static/locales/es-419/common.json (1)

3362-3362: String looks good.

The new key/value pair follows the existing naming pattern for event-status entries and the Spanish phrasing (“Reserva vencida”) is clear and idiomatic. No further action required.

apps/web/public/static/locales/it/common.json (1)

3360-3363: String added correctly – no action needed

The new key "reservation_expired": "Prenotazione scaduta" is syntactically valid, translation is accurate, and the trailing comma keeps the JSON well-formed.
No further changes required.

apps/web/public/static/locales/ta/common.json (1)

213-214: Verify consistency of the new key across all locales

The "reservation_expired" key and its Tamil translation look correct syntactically.
Please double-check that:

  1. The same key exists in every other locale file (including default en), otherwise the UI will display fallback text for missing languages.
  2. No other file misspells the key or introduces trailing/leading spaces.

If you have already run your i18n consistency check, feel free to ignore this note.

packages/prisma/migrations/20250724184730_add_reservation_expired_webhook_enum/migration.sql (1)

1-2: PostgreSQL enum alteration may fail on older versions

ALTER TYPE … ADD VALUE is not permitted inside a transaction block on PostgreSQL < 12.
Prisma usually wraps each migration in a transaction; if your production DB is ≤ 11 this statement will error at deploy time.

Options:

-- 1) Let Prisma generate `--! no-transaction` at the top of the migration file
-- 2) Run the enum alteration in a manual, out-of-band migration step

Confirm the target cluster version or add --! no-transaction to be safe.

apps/web/public/static/locales/he/common.json (1)

3362-3362: LGTM – translation key added correctly

reservation_expired key is introduced with a proper Hebrew translation and follows the existing JSON style (comma-terminated entry before the sentinel comment). No further action required.

apps/web/public/static/locales/fi/common.json (1)

3362-3363: New key added correctly and translation looks good

The "reservation_expired" key is introduced right above the sentinel comment (as required) and the Finnish translation “Varaus vanhentunut” is accurate and concise. No further action needed.

apps/web/public/static/locales/sr/common.json (1)

3362-3362: Serbian translation added correctly

Key/value pair for reservation_expired looks good – accurate Cyrillic translation, alphabet and punctuation are consistent with the rest of the file, and the comma is present to keep JSON valid.
No further action required.

apps/web/public/static/locales/ru/common.json (1)

3362-3362: Looks good – key added in the correct place and translated properly

The new "reservation_expired" entry is syntactically correct, uses the established snake-case convention, is comma-terminated so the sentinel key below remains valid, and the Russian phrasing is accurate. No further action needed.

apps/web/public/static/locales/el/common.json (1)

3362-3362: New locale key looks good

"reservation_expired": "Η κράτηση έληξε" is correctly added and accurately translated. No structural issues found in the JSON.

apps/web/public/static/locales/sk-SK/common.json (1)

3361-3363: String key added correctly; ensure parity with other locale files

The new key "reservation_expired": "Rezervácia vypršala" is syntactically correct and placed immediately above the sentinel line, so the JSON remains valid.

Please double-check that:

  1. The same key exists in all other locale JSON files (including en, which acts as the source of truth).
  2. Any frontend/component code that reads reservation_expired is covered by i18n fallback logic to avoid run-time undefined strings when a translation is missing.

No further action needed in this file.

apps/web/public/static/locales/no/common.json (1)

3362-3362: Translation addition looks good

The new entry is syntactically correct, uses an appropriate Norwegian translation (“Reservasjon utløpt”), and includes the necessary trailing comma to keep the JSON valid.

apps/web/public/static/locales/uk/common.json (1)

3362-3362: Translation looks good – no issues.

The added key "reservation_expired": "Бронювання закінчилося" accurately conveys “Reservation expired,” matches existing tone, and introduces no placeholder/variable mismatches.

apps/web/public/static/locales/fr/common.json (1)

3362-3363: String key added successfully

The new reservation_expired key/value pair looks correct, follows the existing kebab-case naming convention, and the translation reads well.
No further action required.

apps/web/public/static/locales/eu/common.json (1)

3362-3362: Basque translation entry looks good – verify consistency across all locale files
The new "reservation_expired": "Erreserba iraungita" key/value pair is well-formed and the wording is accurate.
To avoid runtime fallback issues, double-check that the same key exists (with appropriate translations) in every other locale JSON before shipping.

apps/web/public/static/locales/ca/common.json (1)

3362-3362: New key added – looks good

The Catalan translation “Reserva caducada” accurately reflects the English key reservation_expired. No issues spotted.

apps/web/public/static/locales/da/common.json (1)

3362-3362: Looks good – translation key and value are consistent

The new entry "reservation_expired": "Reservation udløbet" follows the existing naming / capital-letter style and keeps the JSON valid (comma placement is correct). No further action needed.

apps/web/public/static/locales/nl/common.json (1)

3362-3362: Translation looks good and placement follows the “add-above-sentinel” convention
The Dutch string “Reservering verlopen” correctly mirrors the English “Reservation expired”, and inserting it immediately above ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS preserves merge-conflict hygiene. No action required.

apps/web/public/static/locales/bg/common.json (1)

3362-3362: LGTM – new key and translation look correct

• JSON syntax is valid (trailing comma present).
• Bulgarian copy (“Резервацията е изтекла”) accurately conveys “Reservation expired”.

No further action needed.

apps/web/public/static/locales/lv/common.json (1)

164-165: Comma fix restores valid JSON; translation key added correctly.

The added comma on line 164 resolves an invalid-JSON risk, and the new "reservation_expired" key/value on line 165 follows the existing naming pattern and appears linguistically accurate.

apps/web/public/static/locales/de/common.json (1)

3362-3362: Translation entry looks correct and consistent

The new key/value pair ("reservation_expired": "Reservierung abgelaufen") is unique, linguistically accurate, and follows the existing tone/style of the file. No JSON syntax issues detected.

apps/web/public/static/locales/id/common.json (1)

168-169: New key and JSON comma look good.

The added comma on the previous line and the new reservation_expired key keep the JSON valid and align with the new webhook event. No functional issues found.

apps/web/public/static/locales/cs/common.json (1)

3362-3362: Addition looks correct – no issues found.

The new key reservation_expired is added with an accurate Czech translation and proper trailing comma. JSON structure remains valid.

packages/features/webhooks/lib/constants.ts (1)

23-23: LGTM! Proper placement of the new webhook trigger event.

The RESERVATION_EXPIRED event is correctly added to the core group alongside other booking-related webhook events, following the established pattern.

apps/web/public/static/locales/hr/common.json (1)

456-456: LGTM! Appropriate Croatian translation for the new webhook event.

The translation "Rezervacija je istekla" correctly conveys "Reservation has expired" in Croatian and follows the established localization pattern for new feature strings.

packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts (1)

36-50: LGTM! Proper data fetching for webhook scheduling.

The expanded query correctly fetches the necessary fields (teamId and team.parentId) required for webhook scheduling and organization ID resolution.

packages/features/webhooks/lib/scheduleTrigger.ts (1)

20-29: LGTM! Well-defined type for slot data structure.

The SLOT_TYPE interface properly captures all the necessary slot fields required for webhook scheduling.

@@ -182,7 +182,7 @@ where `{{type}}` represents the event type slug and `{{title}}` represents the t

| Variable | Type | Description |
|--------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTED] |
| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTEDRESERVATION_EXPIRED, RESERVATION_EXPIRED] |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the formatting issue in the webhook trigger event list.

There appears to be a formatting error where FORM_SUBMITTEDRESERVATION_EXPIRED is shown as a single entry. These should be separate entries with proper comma separation.

Apply this diff to fix the formatting:

-| triggerEvent       | String       | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTEDRESERVATION_EXPIRED, RESERVATION_EXPIRED] |
+| triggerEvent       | String       | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTED, RESERVATION_EXPIRED] |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTEDRESERVATION_EXPIRED, RESERVATION_EXPIRED] |
| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED, MEETING_ENDED, BOOKING_REJECTED, BOOKING_REQUESTED, BOOKING_PAYMENT_INITIATED, BOOKING_PAID, MEETING_STARTED, RECORDING_READY, FORM_SUBMITTED, RESERVATION_EXPIRED] |
🤖 Prompt for AI Agents
In docs/developing/guides/automation/webhooks.mdx at line 185, the webhook
trigger event list has a formatting issue where FORM_SUBMITTED and
RESERVATION_EXPIRED are combined without a comma. Separate these into two
distinct entries by adding a comma between FORM_SUBMITTED and
RESERVATION_EXPIRED to fix the list formatting.

@@ -57,6 +57,7 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
{ value: WebhookTriggerEvents.RECORDING_READY, label: "recording_ready" },
{ value: WebhookTriggerEvents.INSTANT_MEETING, label: "instant_meeting" },
{ value: WebhookTriggerEvents.OOO_CREATED, label: "ooo_created" },
{ value: WebhookTriggerEvents.RESERVATION_EXPIRED, label: "reservation_expired" },
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify i18n key exists for new trigger label

label: "reservation_expired" relies on a translation key of the same name.
If this key is missing in any locale JSON, users will see the raw string. Confirm the key was added to all locale files updated in this PR.

You can run the following to double-check:


🏁 Script executed:

#!/bin/bash
# Ensure every locale has the new translation
fd common.json apps/web/public/static/locales | xargs -I{} jq -e '."reservation_expired"' {} >/dev/null || echo "Missing key in: {}"

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Ensure every locale JSON file contains the 'reservation_expired' key.

# Find all common.json files under locales
files=$(fd common.json apps/web/public/static/locales -t f)

if [[ -z "$files" ]]; then
  echo "No locale files found under apps/web/public/static/locales"
  exit 1
fi

echo "Found locale files:"
echo "$files"

echo -e "\nChecking for missing 'reservation_expired' key:"
missing=false
while read -r file; do
  if ! jq -e '."reservation_expired"' "$file" >/dev/null; then
    echo "Missing key in: $file"
    missing=true
  fi
done <<< "$files"

if [[ "$missing" == false ]]; then
  echo "All locale files contain the 'reservation_expired' key."
fi

Length of output: 13058


Translation key missing in some locales
The new trigger label uses the i18n key reservation_expired, but it isn’t defined in all locale files. Missing key found in:

  • apps/web/public/static/locales/bn/common.json
  • apps/web/public/static/locales/iw/common.json
  • apps/web/public/static/locales/th/common.json

Please add the reservation_expired entry (with the appropriate translated string) to each of these JSON files so users don’t see the raw key.

🤖 Prompt for AI Agents
In packages/features/webhooks/components/WebhookForm.tsx at line 60, the label
"reservation_expired" is used as an i18n key but is missing in some locale JSON
files. To fix this, add the "reservation_expired" key with the correct
translated string to the common.json files in the bn, iw, and th locale
directories under apps/web/public/static/locales. This will ensure the label is
properly localized and users do not see the raw key.

Comment on lines +279 to +336
export async function scheduleReservationExpiredTrigger({
slot,
subscriberUrl,
subscriber,
triggerEvent,
isDryRun = false,
}: {
slot: SLOT_TYPE;
subscriberUrl: string;
subscriber: { id: string; appId: string | null };
triggerEvent: typeof WebhookTriggerEvents.RESERVATION_EXPIRED;
isDryRun?: boolean;
}) {
if (isDryRun) return;
try {
const payload = JSON.stringify({ triggerEvent, ...slot });

const { id, releaseAt, isSeat, ...rest } = slot;
const restString = JSON.stringify(rest);
const searchString = restString.slice(1, -1);

const isWebhookScheduledTriggerExists = await prisma.webhookScheduledTriggers.findFirst({
where: {
payload: {
contains: searchString,
},
},
});

if (isWebhookScheduledTriggerExists) {
await prisma.webhookScheduledTriggers.update({
where: {
id: isWebhookScheduledTriggerExists.id,
},
data: {
payload,
startAfter: slot.releaseAt,
},
});
} else {
await prisma.webhookScheduledTriggers.create({
data: {
payload,
appId: subscriber.appId,
startAfter: slot.releaseAt,
subscriberUrl,
webhook: {
connect: {
id: subscriber.id,
},
},
},
});
}
} catch (error) {
console.error("Error scheduling webhook trigger (create/update)", error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve the webhook trigger scheduling implementation.

While the overall logic is sound, there are several areas that need improvement:

  1. Fragile search logic: The string matching approach using JSON.stringify and string slicing is brittle and could break if the JSON structure changes.
  2. Inconsistent error handling: Uses console.error instead of the established logger pattern used elsewhere in the file.
  3. Potential race conditions: The find-then-update pattern could lead to race conditions in concurrent scenarios.

Consider these improvements:

   try {
     const payload = JSON.stringify({ triggerEvent, ...slot });

-    const { id, releaseAt, isSeat, ...rest } = slot;
-    const restString = JSON.stringify(rest);
-    const searchString = restString.slice(1, -1);
-
-    const isWebhookScheduledTriggerExists = await prisma.webhookScheduledTriggers.findFirst({
-      where: {
-        payload: {
-          contains: searchString,
-        },
-      },
-    });
+    // Use more reliable identification based on slot properties
+    const isWebhookScheduledTriggerExists = await prisma.webhookScheduledTriggers.findFirst({
+      where: {
+        AND: [
+          { payload: { contains: `"uid":"${slot.uid}"` } },
+          { payload: { contains: `"userId":${slot.userId}` } },
+          { payload: { contains: `"eventTypeId":${slot.eventTypeId}` } },
+          { payload: { contains: `"triggerEvent":"${triggerEvent}"` } },
+        ],
+      },
+    });

     if (isWebhookScheduledTriggerExists) {
-      await prisma.webhookScheduledTriggers.update({
+      await prisma.webhookScheduledTriggers.upsert({
         where: {
           id: isWebhookScheduledTriggerExists.id,
         },
+        update: {
+          payload,
+          startAfter: slot.releaseAt,
+        },
+        create: {
+          payload,
+          appId: subscriber.appId,
+          startAfter: slot.releaseAt,
+          subscriberUrl,
+          webhook: {
+            connect: {
+              id: subscriber.id,
+            },
+          },
+        },
+      });
+    } else {
+      await prisma.webhookScheduledTriggers.create({
         data: {
           payload,
-          startAfter: slot.releaseAt,
+          appId: subscriber.appId,
+          startAfter: slot.releaseAt,
+          subscriberUrl,
+          webhook: {
+            connect: {
+              id: subscriber.id,
+            },
+          },
         },
       });
-    } else {
-      // ... existing create logic
     }
   } catch (error) {
-    console.error("Error scheduling webhook trigger (create/update)", error);
+    log.error("Error scheduling webhook trigger (create/update)", safeStringify(error));
   }

This approach:

  • Uses more specific field matching instead of fragile JSON string manipulation
  • Uses upsert to handle race conditions
  • Maintains consistent error logging patterns
🤖 Prompt for AI Agents
In packages/features/webhooks/lib/scheduleTrigger.ts between lines 279 and 336,
improve the webhook trigger scheduling by replacing the fragile JSON string
search with direct field matching in the database query, use Prisma's upsert
method instead of separate find and update calls to avoid race conditions, and
replace console.error with the established logger used elsewhere in the file for
consistent error handling.

@giripatel
Copy link
Contributor Author

Hey @kart1ka, I've addressed all your comments from #21997 and created a new PR with only the feature-related changes.

Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

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

Left a comment

Comment on lines +300 to +306
const isWebhookScheduledTriggerExists = await prisma.webhookScheduledTriggers.findFirst({
where: {
payload: {
contains: searchString,
},
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not the right approach to search for existing scheduled trigger and would be very slow in production environment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing that out!

To explore alternatives, I tested the following two approaches on a dataset of ~1 million records:

Prisma .findFirst with contains

Raw SQL query using multiple LIKE clauses on the JSON string

result :

findFirst: 66.687ms
fromRawQuery: 419.159ms

However, I noticed that even the raw SQL approach is quite slow in this case. Since the payload is stored as a JSON string, both approaches seem inefficient for matching structured data.

Could you please suggest the right approach to efficiently search for an existing scheduled trigger?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @kart1ka & @PeerRich ,

Could I add a new column called "searchString" to the WebhookScheduleTriggers table and index it to support search operations during webhook updates?

In this searchString column, we'd store a structure like this for cases involving Reservation Expiry:

"{
uid: value,
userId: value,
eventTypeId: value,
slotUtcStartDate: value,
slotUtcEndDate: value
}"

Let me know if this approach works, or if you have a better suggestion that's more suitable for the product.

@kart1ka kart1ka marked this pull request as draft July 25, 2025 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api area: API, enterprise API, access token, OAuth community Created by Linear-GitHub Sync ✨ feature New feature or request i18n area: i18n, translations ❗️ migrations contains migration files platform Anything related to our platform plan webhooks area: webhooks, callback, webhook payload
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature: expired reservation tracking
2 participants