Skip to content

Commit 8b7497b

Browse files
chore: Return webhook version in the header (#26139)
* init * add tests * fix type * type fix * fix * fix tests * fix test
1 parent 18017b5 commit 8b7497b

File tree

19 files changed

+506
-18
lines changed

19 files changed

+506
-18
lines changed

docs/developing/guides/automation/webhooks.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ All webhook payloads are wrapped in the following structure:
5959
}
6060
```
6161

62+
<Note>
63+
Webhook payloads are versioned. The version of the payload sent is included in the x-cal-webhook-version HTTP header.
64+
Example: x-cal-webhook-version: 2021-10-20
65+
</Note>
66+
6267
Select a version and trigger event to view the example payload:
6368

6469
<Tabs>

packages/features/bookings/lib/handleNewBooking/scheduleNoShowTriggers.integration-test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ describe("scheduleNoShowTriggers Integration", () => {
265265
payloadTemplate: null,
266266
secret: null,
267267
appId: null,
268+
version: "2021-10-20",
268269
},
269270
});
270271

@@ -273,8 +274,8 @@ describe("scheduleNoShowTriggers Integration", () => {
273274
expect(sendGenericWebhookPayload).toHaveBeenCalledWith(
274275
expect.objectContaining({
275276
webhook: expect.objectContaining({
276-
id: hostWebhook.id,
277277
subscriberUrl: "https://example.com/host-webhook",
278+
version: "2021-10-20",
278279
}),
279280
triggerEvent: WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
280281
data: expect.objectContaining({
@@ -287,6 +288,11 @@ describe("scheduleNoShowTriggers Integration", () => {
287288
user_name: "Guest User",
288289
}),
289290
]),
291+
webhook: expect.objectContaining({
292+
id: hostWebhook.id,
293+
subscriberUrl: "https://example.com/host-webhook",
294+
version: "2021-10-20",
295+
}),
290296
}),
291297
})
292298
);

packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getDelegationCredentialOrFindRegularCredential } from "@calcom/app-stor
44
import { sendCancelledSeatEmailsAndSMS } from "@calcom/emails/email-manager";
55
import { updateMeeting } from "@calcom/features/conferencing/lib/videoClient";
66
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
7+
import type { WebhookVersion } from "@calcom/features/webhooks/lib/interface/IWebhookRepository";
78
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
89
import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
910
import { getRichDescription } from "@calcom/lib/CalEventParser";
@@ -31,6 +32,7 @@ async function cancelAttendeeSeat(
3132
payloadTemplate: string | null;
3233
appId: string | null;
3334
secret: string | null;
35+
version: WebhookVersion;
3436
}[];
3537
evt: CalendarEvent;
3638
eventTypeInfo: EventTypeInfo;

packages/features/bookings/lib/service/InstantBookingCreateService.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Prisma } from "@calcom/prisma/client";
2727
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
2828

2929
import { instantMeetingSubscriptionSchema as subscriptionSchema } from "../dto/schema";
30+
import { WebhookVersion } from "../../../webhooks/lib/interface/IWebhookRepository";
3031

3132
interface IInstantBookingCreateServiceDependencies {
3233
prismaClient: PrismaClient;
@@ -70,6 +71,7 @@ const handleInstantMeetingWebhookTrigger = async (args: {
7071
payloadTemplate: true,
7172
appId: true,
7273
secret: true,
74+
version: true,
7375
},
7476
});
7577

@@ -80,7 +82,10 @@ const handleInstantMeetingWebhookTrigger = async (args: {
8082
secretKey: sub.secret,
8183
triggerEvent: eventTrigger,
8284
createdAt: new Date().toISOString(),
83-
webhook: sub,
85+
webhook: {
86+
...sub,
87+
version: sub.version as WebhookVersion,
88+
},
8489
data: webhookData,
8590
}).catch((e) => {
8691
console.error(

packages/features/tasker/tasks/sendWebook.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from "zod";
22

3+
import { WebhookVersion } from "@calcom/features/webhooks/lib/interface/IWebhookRepository";
34
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
45

56
const sendWebhookPayloadSchema = z.object({
@@ -10,6 +11,7 @@ const sendWebhookPayloadSchema = z.object({
1011
subscriberUrl: z.string().url(),
1112
appId: z.string().nullable(),
1213
payloadTemplate: z.string().nullable(),
14+
version: z.nativeEnum(WebhookVersion),
1315
}),
1416
// TODO: Define the data schema
1517
data: z.any(),
@@ -22,7 +24,6 @@ export async function sendWebhook(payload: string): Promise<void> {
2224
);
2325
await sendPayload(secretKey, triggerEvent, createdAt, webhook, data);
2426
} catch (error) {
25-
// ... handle error
2627
console.error(error);
2728
throw error;
2829
}

packages/features/tasker/tasks/triggerFormSubmittedNoEvent/triggerFormSubmittedNoEventWebhook.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from "zod";
22

33
import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
44
import incompleteBookingActionFunctions from "@calcom/app-store/routing-forms/lib/incompleteBooking/actionFunctions";
5+
import { DEFAULT_WEBHOOK_VERSION, WebhookVersion } from "@calcom/features/webhooks/lib/interface/IWebhookRepository";
56
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
67
import prisma from "@calcom/prisma";
78

@@ -23,6 +24,7 @@ export const ZTriggerFormSubmittedNoEventWebhookPayloadSchema = z.object({
2324
appId: z.string().nullable(),
2425
payloadTemplate: z.string().nullable(),
2526
secret: z.string().nullable(),
27+
version: z.nativeEnum(WebhookVersion).optional(),
2628
}),
2729
responseId: z.number(),
2830
responses: z.any(),
@@ -55,7 +57,12 @@ export async function triggerFormSubmittedNoEventWebhook(payload: string): Promi
5557
secretKey: webhook.secret,
5658
triggerEvent: "FORM_SUBMITTED_NO_EVENT",
5759
createdAt: new Date().toISOString(),
58-
webhook,
60+
webhook: {
61+
subscriberUrl: webhook.subscriberUrl,
62+
appId: webhook.appId,
63+
payloadTemplate: webhook.payloadTemplate,
64+
version: webhook.version ?? DEFAULT_WEBHOOK_VERSION,
65+
},
5966
data: {
6067
formId: form.id,
6168
formName: form.name,

packages/features/tasker/tasks/triggerNoShow/common.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,19 @@ export function sendWebhookPayload(
3333
participants: ParticipantsWithEmail,
3434
originalRescheduledBooking?: OriginalRescheduledBooking,
3535
hostEmail?: string
36-
): Promise<any> {
36+
): Promise<{ ok: boolean; status: number } | void> {
3737
const maxStartTimeHumanReadable = dayjs.unix(maxStartTime).format("YYYY-MM-DD HH:mm:ss Z");
3838

3939
return sendGenericWebhookPayload({
4040
secretKey: webhook.secret,
4141
triggerEvent,
4242
createdAt: new Date().toISOString(),
43-
webhook,
43+
webhook: {
44+
subscriberUrl: webhook.subscriberUrl,
45+
appId: webhook.appId,
46+
payloadTemplate: webhook.payloadTemplate,
47+
version: webhook.version,
48+
},
4449
data: {
4550
title: booking.title,
4651
bookingId: booking.id,

packages/features/tasker/tasks/triggerNoShow/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from "zod";
22

33
import { TIME_UNIT } from "@calcom/features/ee/workflows/lib/constants";
44
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
5+
import { WebhookVersion } from "../../../webhooks/lib/interface/IWebhookRepository";
56

67
const commonSchema = z.object({
78
triggerEvent: z.enum([
@@ -20,6 +21,7 @@ export const ZWebhook = z.object({
2021
timeUnit: z.enum(TIME_UNIT),
2122
eventTriggers: z.array(z.string()),
2223
payloadTemplate: z.string().nullable(),
24+
version: z.nativeEnum(WebhookVersion),
2325
});
2426

2527
export type TWebhook = z.infer<typeof ZWebhook>;

packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { calculateMaxStartTime } from "./common";
2121
import { getMeetingSessionsFromRoomName } from "./getMeetingSessionsFromRoomName";
2222
import type { TSendNoShowWebhookPayloadSchema } from "./schema";
2323
import { triggerGuestNoShow } from "./triggerGuestNoShow";
24+
import { WebhookVersion } from "../../../webhooks/lib/interface/IWebhookRepository";
2425

2526
vi.mock("@calcom/features/tasker/tasks/triggerNoShow/getMeetingSessionsFromRoomName", () => ({
2627
getMeetingSessionsFromRoomName: vi.fn(),
@@ -141,6 +142,7 @@ describe("Trigger Guest No Show:", () => {
141142
timeUnit: TimeUnit.MINUTE,
142143
payloadTemplate: null,
143144
secret: null,
145+
version: WebhookVersion.V_2021_10_20,
144146
};
145147

146148
const payload = JSON.stringify({
@@ -327,6 +329,7 @@ describe("Trigger Guest No Show:", () => {
327329
timeUnit: TimeUnit.MINUTE,
328330
payloadTemplate: null,
329331
secret: null,
332+
version: WebhookVersion.V_2021_10_20,
330333
};
331334

332335
const payload = JSON.stringify({
@@ -552,6 +555,7 @@ describe("Trigger Guest No Show:", () => {
552555
timeUnit: TimeUnit.MINUTE,
553556
payloadTemplate: null,
554557
secret: null,
558+
version: WebhookVersion.V_2021_10_20,
555559
};
556560

557561
const payload = JSON.stringify({
@@ -737,6 +741,7 @@ describe("Trigger Guest No Show:", () => {
737741
timeUnit: TimeUnit.MINUTE,
738742
payloadTemplate: null,
739743
secret: null,
744+
version: WebhookVersion.V_2021_10_20,
740745
};
741746

742747
const payload = JSON.stringify({
@@ -889,6 +894,7 @@ describe("Trigger Guest No Show:", () => {
889894
timeUnit: TimeUnit.MINUTE,
890895
payloadTemplate: null,
891896
secret: null,
897+
version: WebhookVersion.V_2021_10_20,
892898
};
893899

894900
const payload = JSON.stringify({

packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { calculateMaxStartTime } from "./common";
2121
import { getMeetingSessionsFromRoomName } from "./getMeetingSessionsFromRoomName";
2222
import type { TSendNoShowWebhookPayloadSchema } from "./schema";
2323
import { triggerHostNoShow } from "./triggerHostNoShow";
24+
import { WebhookVersion } from "../../../webhooks/lib/interface/IWebhookRepository";
2425

2526
vi.mock("@calcom/features/tasker/tasks/triggerNoShow/getMeetingSessionsFromRoomName", () => ({
2627
getMeetingSessionsFromRoomName: vi.fn(),
@@ -133,6 +134,7 @@ describe("Trigger Host No Show:", () => {
133134
timeUnit: TimeUnit.MINUTE,
134135
payloadTemplate: null,
135136
secret: null,
137+
version: WebhookVersion.V_2021_10_20,
136138
};
137139

138140
const payload = JSON.stringify({
@@ -298,6 +300,7 @@ describe("Trigger Host No Show:", () => {
298300
timeUnit: TimeUnit.MINUTE,
299301
payloadTemplate: null,
300302
secret: null,
303+
version: WebhookVersion.V_2021_10_20,
301304
};
302305

303306
const payload = JSON.stringify({
@@ -503,6 +506,7 @@ describe("Trigger Host No Show:", () => {
503506
timeUnit: TimeUnit.MINUTE,
504507
payloadTemplate: null,
505508
secret: null,
509+
version: WebhookVersion.V_2021_10_20,
506510
};
507511

508512
const payload = JSON.stringify({
@@ -653,6 +657,7 @@ describe("Trigger Host No Show:", () => {
653657
timeUnit: TimeUnit.MINUTE,
654658
payloadTemplate: null,
655659
secret: null,
660+
version: WebhookVersion.V_2021_10_20,
656661
};
657662

658663
const payload = JSON.stringify({

0 commit comments

Comments
 (0)