-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
base: main
Are you sure you want to change the base?
feat: handle reservation expiry in webhooks #22724
Conversation
@giripatel is attempting to deploy a commit to the cal Team on Vercel. A member of the Team first needs to authorize it. |
""" WalkthroughThis change introduces the "reservation expired" webhook event throughout the application. It updates the database schema and Prisma enum to include Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18 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
packages/trpc/server/routers/viewer/slots/reserveSlot.handler.test.tsOops! 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:
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 detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
⏰ 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)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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. |
There was a problem hiding this 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” stateUsing 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 keyreservation_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 consistencyThe 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., placingemail_survey_triggered_by_workflow
alongside the otheremail_*
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:
- Performance: Using
await
insidemap()
creates sequential execution. For team events with many users, this could be slow.- 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
📒 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 neededThe 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 markerThe
"reservation_expired"
translation is inserted just before theADD_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 valueRESERVATION_EXPIRED
– looks correct and consistentThe 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 anyswitch(WebhookTriggerEvents…)
statements, and most trigger‐handling is implemented viaif/else
checks and array literals. Please manually confirm that every codepath comparing or listingWebhookTriggerEvents
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 underpackages/features/tasker
andpackages/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 correctlyThe 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 goodThe 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 correctlyThe 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.
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.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 spottedThe 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 correctlyThe 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 syncThe new
"reservation_expired"
entry follows the existing naming/style conventions and is correctly placed before the merge-conflict sentinel.
Just double-check that:
- All other locale JSONs include the same key (already mentioned in the PR description).
- 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 enumThe 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 neededThe 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 localesThe
"reservation_expired"
key and its Tamil translation look correct syntactically.
Please double-check that:
- The same key exists in every other locale file (including default
en
), otherwise the UI will display fallback text for missing languages.- 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 stepConfirm 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 goodThe
"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 correctlyKey/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 properlyThe 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 filesThe 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:
- The same key exists in all other locale JSON files (including
en
, which acts as the source of truth).- Any frontend/component code that reads
reservation_expired
is covered by i18n fallback logic to avoid run-timeundefined
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 goodThe 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 successfullyThe 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 goodThe 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 consistentThe 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 aboveADD_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 consistentThe 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
andteam.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] | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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" }, |
There was a problem hiding this comment.
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.
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); | ||
} | ||
} |
There was a problem hiding this comment.
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:
- Fragile search logic: The string matching approach using
JSON.stringify
and string slicing is brittle and could break if the JSON structure changes. - Inconsistent error handling: Uses
console.error
instead of the established logger pattern used elsewhere in the file. - 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.
…servation-tracking
…servation-tracking
…servation-tracking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment
const isWebhookScheduledTriggerExists = await prisma.webhookScheduledTriggers.findFirst({ | ||
where: { | ||
payload: { | ||
contains: searchString, | ||
}, | ||
}, | ||
}); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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:
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Summary by cubic
Added a new webhook event that triggers when a reservation expires, allowing external systems to get notified in real time.