@webhooks-cc/sdk 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{diff-BlDnipA4.d.mts → diff-C6vrhcEq.d.mts} +2 -2
- package/dist/{diff-BlDnipA4.d.ts → diff-C6vrhcEq.d.ts} +2 -2
- package/dist/index.d.mts +74 -3
- package/dist/index.d.ts +74 -3
- package/dist/index.js +518 -4
- package/dist/index.mjs +510 -3
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,15 +31,19 @@ __export(src_exports, {
|
|
|
31
31
|
WebhooksCCError: () => WebhooksCCError,
|
|
32
32
|
diffRequests: () => diffRequests,
|
|
33
33
|
extractJsonField: () => extractJsonField,
|
|
34
|
+
isClerkWebhook: () => isClerkWebhook,
|
|
34
35
|
isDiscordWebhook: () => isDiscordWebhook,
|
|
35
36
|
isGitHubWebhook: () => isGitHubWebhook,
|
|
37
|
+
isGitLabWebhook: () => isGitLabWebhook,
|
|
36
38
|
isLinearWebhook: () => isLinearWebhook,
|
|
37
39
|
isPaddleWebhook: () => isPaddleWebhook,
|
|
40
|
+
isSendGridWebhook: () => isSendGridWebhook,
|
|
38
41
|
isShopifyWebhook: () => isShopifyWebhook,
|
|
39
42
|
isSlackWebhook: () => isSlackWebhook,
|
|
40
43
|
isStandardWebhook: () => isStandardWebhook,
|
|
41
44
|
isStripeWebhook: () => isStripeWebhook,
|
|
42
45
|
isTwilioWebhook: () => isTwilioWebhook,
|
|
46
|
+
isVercelWebhook: () => isVercelWebhook,
|
|
43
47
|
matchAll: () => matchAll,
|
|
44
48
|
matchAny: () => matchAny,
|
|
45
49
|
matchBodyPath: () => matchBodyPath,
|
|
@@ -55,8 +59,10 @@ __export(src_exports, {
|
|
|
55
59
|
parseFormBody: () => parseFormBody,
|
|
56
60
|
parseJsonBody: () => parseJsonBody,
|
|
57
61
|
parseSSE: () => parseSSE,
|
|
62
|
+
verifyClerkSignature: () => verifyClerkSignature,
|
|
58
63
|
verifyDiscordSignature: () => verifyDiscordSignature,
|
|
59
64
|
verifyGitHubSignature: () => verifyGitHubSignature,
|
|
65
|
+
verifyGitLabSignature: () => verifyGitLabSignature,
|
|
60
66
|
verifyLinearSignature: () => verifyLinearSignature,
|
|
61
67
|
verifyPaddleSignature: () => verifyPaddleSignature,
|
|
62
68
|
verifyShopifySignature: () => verifyShopifySignature,
|
|
@@ -64,7 +70,8 @@ __export(src_exports, {
|
|
|
64
70
|
verifySlackSignature: () => verifySlackSignature,
|
|
65
71
|
verifyStandardWebhookSignature: () => verifyStandardWebhookSignature,
|
|
66
72
|
verifyStripeSignature: () => verifyStripeSignature,
|
|
67
|
-
verifyTwilioSignature: () => verifyTwilioSignature
|
|
73
|
+
verifyTwilioSignature: () => verifyTwilioSignature,
|
|
74
|
+
verifyVercelSignature: () => verifyVercelSignature
|
|
68
75
|
});
|
|
69
76
|
module.exports = __toCommonJS(src_exports);
|
|
70
77
|
|
|
@@ -225,7 +232,12 @@ var DEFAULT_TEMPLATE_BY_PROVIDER = {
|
|
|
225
232
|
twilio: "messaging.inbound",
|
|
226
233
|
slack: "event_callback",
|
|
227
234
|
paddle: "transaction.completed",
|
|
228
|
-
linear: "issue.create"
|
|
235
|
+
linear: "issue.create",
|
|
236
|
+
sendgrid: "delivered",
|
|
237
|
+
clerk: "user.created",
|
|
238
|
+
discord: "interaction_create",
|
|
239
|
+
vercel: "deployment.created",
|
|
240
|
+
gitlab: "push"
|
|
229
241
|
};
|
|
230
242
|
var PROVIDER_TEMPLATES = {
|
|
231
243
|
stripe: ["payment_intent.succeeded", "checkout.session.completed", "invoice.paid"],
|
|
@@ -234,7 +246,12 @@ var PROVIDER_TEMPLATES = {
|
|
|
234
246
|
twilio: ["messaging.inbound", "messaging.status_callback", "voice.incoming_call"],
|
|
235
247
|
slack: ["event_callback", "slash_command", "url_verification"],
|
|
236
248
|
paddle: ["transaction.completed", "subscription.created", "subscription.updated"],
|
|
237
|
-
linear: ["issue.create", "issue.update", "comment.create"]
|
|
249
|
+
linear: ["issue.create", "issue.update", "comment.create"],
|
|
250
|
+
sendgrid: ["delivered", "open", "bounce", "spam_report"],
|
|
251
|
+
clerk: ["user.created", "user.updated", "user.deleted", "session.created"],
|
|
252
|
+
discord: ["interaction_create", "message_component", "ping"],
|
|
253
|
+
vercel: ["deployment.created", "deployment.succeeded", "deployment.error"],
|
|
254
|
+
gitlab: ["push", "merge_request"]
|
|
238
255
|
};
|
|
239
256
|
var TEMPLATE_PROVIDERS = [
|
|
240
257
|
"stripe",
|
|
@@ -244,6 +261,11 @@ var TEMPLATE_PROVIDERS = [
|
|
|
244
261
|
"slack",
|
|
245
262
|
"paddle",
|
|
246
263
|
"linear",
|
|
264
|
+
"sendgrid",
|
|
265
|
+
"clerk",
|
|
266
|
+
"discord",
|
|
267
|
+
"vercel",
|
|
268
|
+
"gitlab",
|
|
247
269
|
"standard-webhooks"
|
|
248
270
|
];
|
|
249
271
|
var TEMPLATE_METADATA = Object.freeze({
|
|
@@ -303,6 +325,42 @@ var TEMPLATE_METADATA = Object.freeze({
|
|
|
303
325
|
signatureHeader: "linear-signature",
|
|
304
326
|
signatureAlgorithm: "hmac-sha256"
|
|
305
327
|
}),
|
|
328
|
+
sendgrid: Object.freeze({
|
|
329
|
+
provider: "sendgrid",
|
|
330
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.sendgrid]),
|
|
331
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.sendgrid,
|
|
332
|
+
secretRequired: false
|
|
333
|
+
}),
|
|
334
|
+
clerk: Object.freeze({
|
|
335
|
+
provider: "clerk",
|
|
336
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.clerk]),
|
|
337
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.clerk,
|
|
338
|
+
secretRequired: true,
|
|
339
|
+
signatureHeader: "webhook-signature",
|
|
340
|
+
signatureAlgorithm: "hmac-sha256"
|
|
341
|
+
}),
|
|
342
|
+
discord: Object.freeze({
|
|
343
|
+
provider: "discord",
|
|
344
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.discord]),
|
|
345
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.discord,
|
|
346
|
+
secretRequired: false
|
|
347
|
+
}),
|
|
348
|
+
vercel: Object.freeze({
|
|
349
|
+
provider: "vercel",
|
|
350
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.vercel]),
|
|
351
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.vercel,
|
|
352
|
+
secretRequired: true,
|
|
353
|
+
signatureHeader: "x-vercel-signature",
|
|
354
|
+
signatureAlgorithm: "hmac-sha1"
|
|
355
|
+
}),
|
|
356
|
+
gitlab: Object.freeze({
|
|
357
|
+
provider: "gitlab",
|
|
358
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.gitlab]),
|
|
359
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.gitlab,
|
|
360
|
+
secretRequired: true,
|
|
361
|
+
signatureHeader: "x-gitlab-token",
|
|
362
|
+
signatureAlgorithm: "token"
|
|
363
|
+
}),
|
|
306
364
|
"standard-webhooks": Object.freeze({
|
|
307
365
|
provider: "standard-webhooks",
|
|
308
366
|
templates: Object.freeze([]),
|
|
@@ -888,6 +946,228 @@ function buildTemplatePayload(provider, template, event, now, bodyOverride) {
|
|
|
888
946
|
}
|
|
889
947
|
};
|
|
890
948
|
}
|
|
949
|
+
if (provider === "clerk") {
|
|
950
|
+
const userId = `user_${randomHex(24)}`;
|
|
951
|
+
const payloadByTemplate = {
|
|
952
|
+
"user.created": {
|
|
953
|
+
data: {
|
|
954
|
+
id: userId,
|
|
955
|
+
object: "user",
|
|
956
|
+
email_addresses: [
|
|
957
|
+
{
|
|
958
|
+
id: `idn_${randomHex(24)}`,
|
|
959
|
+
email_address: "user@example.com",
|
|
960
|
+
verification: { status: "verified", strategy: "email_code" }
|
|
961
|
+
}
|
|
962
|
+
],
|
|
963
|
+
first_name: "Jane",
|
|
964
|
+
last_name: "Doe",
|
|
965
|
+
created_at: nowSec * 1e3,
|
|
966
|
+
updated_at: nowSec * 1e3
|
|
967
|
+
},
|
|
968
|
+
object: "event",
|
|
969
|
+
type: "user.created",
|
|
970
|
+
timestamp: nowSec * 1e3
|
|
971
|
+
},
|
|
972
|
+
"user.updated": {
|
|
973
|
+
data: {
|
|
974
|
+
id: userId,
|
|
975
|
+
object: "user",
|
|
976
|
+
email_addresses: [
|
|
977
|
+
{
|
|
978
|
+
id: `idn_${randomHex(24)}`,
|
|
979
|
+
email_address: "user@example.com",
|
|
980
|
+
verification: { status: "verified", strategy: "email_code" }
|
|
981
|
+
}
|
|
982
|
+
],
|
|
983
|
+
first_name: "Jane",
|
|
984
|
+
last_name: "Smith",
|
|
985
|
+
created_at: nowSec * 1e3,
|
|
986
|
+
updated_at: nowSec * 1e3
|
|
987
|
+
},
|
|
988
|
+
object: "event",
|
|
989
|
+
type: "user.updated",
|
|
990
|
+
timestamp: nowSec * 1e3
|
|
991
|
+
},
|
|
992
|
+
"user.deleted": {
|
|
993
|
+
data: {
|
|
994
|
+
id: userId,
|
|
995
|
+
object: "user",
|
|
996
|
+
deleted: true
|
|
997
|
+
},
|
|
998
|
+
object: "event",
|
|
999
|
+
type: "user.deleted",
|
|
1000
|
+
timestamp: nowSec * 1e3
|
|
1001
|
+
},
|
|
1002
|
+
"session.created": {
|
|
1003
|
+
data: {
|
|
1004
|
+
id: `sess_${randomHex(24)}`,
|
|
1005
|
+
object: "session",
|
|
1006
|
+
user_id: userId,
|
|
1007
|
+
status: "active",
|
|
1008
|
+
created_at: nowSec * 1e3,
|
|
1009
|
+
updated_at: nowSec * 1e3,
|
|
1010
|
+
expire_at: (nowSec + 86400) * 1e3
|
|
1011
|
+
},
|
|
1012
|
+
object: "event",
|
|
1013
|
+
type: "session.created",
|
|
1014
|
+
timestamp: nowSec * 1e3
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
1018
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1019
|
+
return {
|
|
1020
|
+
body,
|
|
1021
|
+
contentType: "application/json",
|
|
1022
|
+
headers: {
|
|
1023
|
+
"user-agent": "Svix-Webhooks/1.0"
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
if (provider === "vercel") {
|
|
1028
|
+
const deploymentId = `dpl_${randomHex(20)}`;
|
|
1029
|
+
const projectId = `prj_${randomHex(20)}`;
|
|
1030
|
+
const teamId = `team_${randomHex(20)}`;
|
|
1031
|
+
const payloadByTemplate = {
|
|
1032
|
+
"deployment.created": {
|
|
1033
|
+
id: randomUuid(),
|
|
1034
|
+
type: "deployment.created",
|
|
1035
|
+
createdAt: nowSec * 1e3,
|
|
1036
|
+
payload: {
|
|
1037
|
+
deployment: {
|
|
1038
|
+
id: deploymentId,
|
|
1039
|
+
name: "webhooks-cc-web",
|
|
1040
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
1041
|
+
meta: {
|
|
1042
|
+
githubCommitRef: "main",
|
|
1043
|
+
githubCommitSha: randomHex(40),
|
|
1044
|
+
githubCommitMessage: "Update webhook templates"
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
1048
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
"deployment.succeeded": {
|
|
1052
|
+
id: randomUuid(),
|
|
1053
|
+
type: "deployment.succeeded",
|
|
1054
|
+
createdAt: nowSec * 1e3,
|
|
1055
|
+
payload: {
|
|
1056
|
+
deployment: {
|
|
1057
|
+
id: deploymentId,
|
|
1058
|
+
name: "webhooks-cc-web",
|
|
1059
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
1060
|
+
readyState: "READY"
|
|
1061
|
+
},
|
|
1062
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
1063
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
"deployment.error": {
|
|
1067
|
+
id: randomUuid(),
|
|
1068
|
+
type: "deployment.error",
|
|
1069
|
+
createdAt: nowSec * 1e3,
|
|
1070
|
+
payload: {
|
|
1071
|
+
deployment: {
|
|
1072
|
+
id: deploymentId,
|
|
1073
|
+
name: "webhooks-cc-web",
|
|
1074
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
1075
|
+
readyState: "ERROR",
|
|
1076
|
+
errorMessage: "Build failed: exit code 1"
|
|
1077
|
+
},
|
|
1078
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
1079
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
1084
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1085
|
+
return {
|
|
1086
|
+
body,
|
|
1087
|
+
contentType: "application/json",
|
|
1088
|
+
headers: {
|
|
1089
|
+
"user-agent": "Vercel/1.0"
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (provider === "gitlab") {
|
|
1094
|
+
const projectId = Number(randomDigits(7));
|
|
1095
|
+
const payloadByTemplate = {
|
|
1096
|
+
push: {
|
|
1097
|
+
object_kind: "push",
|
|
1098
|
+
event_name: "push",
|
|
1099
|
+
before: randomHex(40),
|
|
1100
|
+
after: randomHex(40),
|
|
1101
|
+
ref: "refs/heads/main",
|
|
1102
|
+
checkout_sha: randomHex(40),
|
|
1103
|
+
user_id: Number(randomDigits(5)),
|
|
1104
|
+
user_name: "webhooks-cc-bot",
|
|
1105
|
+
user_email: "bot@webhooks.cc",
|
|
1106
|
+
project_id: projectId,
|
|
1107
|
+
project: {
|
|
1108
|
+
id: projectId,
|
|
1109
|
+
name: "demo-repo",
|
|
1110
|
+
web_url: "https://gitlab.com/webhooks-cc/demo-repo",
|
|
1111
|
+
namespace: "webhooks-cc",
|
|
1112
|
+
default_branch: "main"
|
|
1113
|
+
},
|
|
1114
|
+
commits: [
|
|
1115
|
+
{
|
|
1116
|
+
id: randomHex(40),
|
|
1117
|
+
message: "Update webhook integration tests",
|
|
1118
|
+
title: "Update webhook integration tests",
|
|
1119
|
+
timestamp: nowIso,
|
|
1120
|
+
url: `https://gitlab.com/webhooks-cc/demo-repo/-/commit/${randomHex(40)}`,
|
|
1121
|
+
author: { name: "webhooks-cc-bot", email: "bot@webhooks.cc" },
|
|
1122
|
+
added: [],
|
|
1123
|
+
modified: ["src/webhooks.ts"],
|
|
1124
|
+
removed: []
|
|
1125
|
+
}
|
|
1126
|
+
],
|
|
1127
|
+
total_commits_count: 1
|
|
1128
|
+
},
|
|
1129
|
+
merge_request: {
|
|
1130
|
+
object_kind: "merge_request",
|
|
1131
|
+
event_type: "merge_request",
|
|
1132
|
+
user: {
|
|
1133
|
+
id: Number(randomDigits(5)),
|
|
1134
|
+
name: "webhooks-cc-bot",
|
|
1135
|
+
username: "webhooks-cc-bot",
|
|
1136
|
+
email: "bot@webhooks.cc"
|
|
1137
|
+
},
|
|
1138
|
+
project: {
|
|
1139
|
+
id: projectId,
|
|
1140
|
+
name: "demo-repo",
|
|
1141
|
+
web_url: "https://gitlab.com/webhooks-cc/demo-repo",
|
|
1142
|
+
namespace: "webhooks-cc",
|
|
1143
|
+
default_branch: "main"
|
|
1144
|
+
},
|
|
1145
|
+
object_attributes: {
|
|
1146
|
+
id: Number(randomDigits(7)),
|
|
1147
|
+
iid: 42,
|
|
1148
|
+
title: "Add webhook retry logic",
|
|
1149
|
+
description: "This MR improves retry handling for inbound webhooks.",
|
|
1150
|
+
state: "opened",
|
|
1151
|
+
action: "open",
|
|
1152
|
+
source_branch: "feature/webhook-retries",
|
|
1153
|
+
target_branch: "main",
|
|
1154
|
+
created_at: nowIso,
|
|
1155
|
+
updated_at: nowIso,
|
|
1156
|
+
url: `https://gitlab.com/webhooks-cc/demo-repo/-/merge_requests/42`
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
1161
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1162
|
+
return {
|
|
1163
|
+
body,
|
|
1164
|
+
contentType: "application/json",
|
|
1165
|
+
headers: {
|
|
1166
|
+
"x-gitlab-event": template === "merge_request" ? "Merge Request Hook" : "Push Hook",
|
|
1167
|
+
"user-agent": "GitLab/1.0"
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
891
1171
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
892
1172
|
}
|
|
893
1173
|
const defaultTwilioParamsByTemplate = {
|
|
@@ -1073,6 +1353,142 @@ async function buildTemplateSendOptions(endpointUrl, options) {
|
|
|
1073
1353
|
body
|
|
1074
1354
|
};
|
|
1075
1355
|
}
|
|
1356
|
+
if (options.provider === "sendgrid") {
|
|
1357
|
+
const method2 = (options.method ?? "POST").toUpperCase();
|
|
1358
|
+
const supported = PROVIDER_TEMPLATES.sendgrid;
|
|
1359
|
+
const template2 = options.template ?? DEFAULT_TEMPLATE_BY_PROVIDER.sendgrid;
|
|
1360
|
+
if (!supported.some((item) => item === template2)) {
|
|
1361
|
+
throw new Error(
|
|
1362
|
+
`Unsupported template "${template2}" for provider "sendgrid". Supported templates: ${supported.join(", ")}`
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1366
|
+
const payloadByTemplate = {
|
|
1367
|
+
delivered: [
|
|
1368
|
+
{
|
|
1369
|
+
sg_event_id: randomHex(22),
|
|
1370
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1371
|
+
email: "recipient@example.com",
|
|
1372
|
+
timestamp: nowSec,
|
|
1373
|
+
event: "delivered",
|
|
1374
|
+
smtp_id: `<${randomHex(20)}@example.com>`,
|
|
1375
|
+
ip: "168.1.1.1",
|
|
1376
|
+
response: "250 OK",
|
|
1377
|
+
category: ["webhooks-cc-test"]
|
|
1378
|
+
}
|
|
1379
|
+
],
|
|
1380
|
+
open: [
|
|
1381
|
+
{
|
|
1382
|
+
sg_event_id: randomHex(22),
|
|
1383
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1384
|
+
email: "recipient@example.com",
|
|
1385
|
+
timestamp: nowSec,
|
|
1386
|
+
event: "open",
|
|
1387
|
+
ip: "72.14.199.28",
|
|
1388
|
+
useragent: "Mozilla/5.0",
|
|
1389
|
+
category: ["webhooks-cc-test"]
|
|
1390
|
+
}
|
|
1391
|
+
],
|
|
1392
|
+
bounce: [
|
|
1393
|
+
{
|
|
1394
|
+
sg_event_id: randomHex(22),
|
|
1395
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1396
|
+
email: "bounced@example.com",
|
|
1397
|
+
timestamp: nowSec,
|
|
1398
|
+
event: "bounce",
|
|
1399
|
+
type: "bounce",
|
|
1400
|
+
status: "5.1.1",
|
|
1401
|
+
reason: "550 5.1.1 The email account does not exist.",
|
|
1402
|
+
ip: "168.1.1.1",
|
|
1403
|
+
category: ["webhooks-cc-test"]
|
|
1404
|
+
}
|
|
1405
|
+
],
|
|
1406
|
+
spam_report: [
|
|
1407
|
+
{
|
|
1408
|
+
sg_event_id: randomHex(22),
|
|
1409
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1410
|
+
email: "complainant@example.com",
|
|
1411
|
+
timestamp: nowSec,
|
|
1412
|
+
event: "spamreport",
|
|
1413
|
+
category: ["webhooks-cc-test"]
|
|
1414
|
+
}
|
|
1415
|
+
]
|
|
1416
|
+
};
|
|
1417
|
+
const payload = options.body ?? payloadByTemplate[template2];
|
|
1418
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1419
|
+
return {
|
|
1420
|
+
method: method2,
|
|
1421
|
+
headers: {
|
|
1422
|
+
"content-type": "application/json",
|
|
1423
|
+
"user-agent": "SendGrid/1.0",
|
|
1424
|
+
"x-webhooks-cc-template-provider": "sendgrid",
|
|
1425
|
+
"x-webhooks-cc-template-template": template2,
|
|
1426
|
+
"x-webhooks-cc-template-event": template2,
|
|
1427
|
+
...options.headers ?? {}
|
|
1428
|
+
},
|
|
1429
|
+
body
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
if (options.provider === "discord") {
|
|
1433
|
+
const method2 = (options.method ?? "POST").toUpperCase();
|
|
1434
|
+
const supported = PROVIDER_TEMPLATES.discord;
|
|
1435
|
+
const template2 = options.template ?? DEFAULT_TEMPLATE_BY_PROVIDER.discord;
|
|
1436
|
+
if (!supported.some((item) => item === template2)) {
|
|
1437
|
+
throw new Error(
|
|
1438
|
+
`Unsupported template "${template2}" for provider "discord". Supported templates: ${supported.join(", ")}`
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1442
|
+
const payloadByTemplate = {
|
|
1443
|
+
interaction_create: {
|
|
1444
|
+
id: randomHex(18),
|
|
1445
|
+
application_id: randomHex(18),
|
|
1446
|
+
type: 2,
|
|
1447
|
+
data: {
|
|
1448
|
+
id: randomHex(18),
|
|
1449
|
+
name: "webhook-test",
|
|
1450
|
+
type: 1
|
|
1451
|
+
},
|
|
1452
|
+
guild_id: randomHex(18),
|
|
1453
|
+
channel_id: randomHex(18),
|
|
1454
|
+
token: randomHex(40),
|
|
1455
|
+
version: 1
|
|
1456
|
+
},
|
|
1457
|
+
message_component: {
|
|
1458
|
+
id: randomHex(18),
|
|
1459
|
+
application_id: randomHex(18),
|
|
1460
|
+
type: 3,
|
|
1461
|
+
data: {
|
|
1462
|
+
custom_id: "click_me",
|
|
1463
|
+
component_type: 2
|
|
1464
|
+
},
|
|
1465
|
+
guild_id: randomHex(18),
|
|
1466
|
+
channel_id: randomHex(18),
|
|
1467
|
+
token: randomHex(40),
|
|
1468
|
+
version: 1
|
|
1469
|
+
},
|
|
1470
|
+
ping: {
|
|
1471
|
+
id: randomHex(18),
|
|
1472
|
+
application_id: randomHex(18),
|
|
1473
|
+
type: 1,
|
|
1474
|
+
version: 1
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
const payload = options.body ?? payloadByTemplate[template2];
|
|
1478
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1479
|
+
return {
|
|
1480
|
+
method: method2,
|
|
1481
|
+
headers: {
|
|
1482
|
+
"content-type": "application/json",
|
|
1483
|
+
"x-signature-timestamp": String(timestamp),
|
|
1484
|
+
"x-webhooks-cc-template-provider": "discord",
|
|
1485
|
+
"x-webhooks-cc-template-template": template2,
|
|
1486
|
+
"x-webhooks-cc-template-event": template2,
|
|
1487
|
+
...options.headers ?? {}
|
|
1488
|
+
},
|
|
1489
|
+
body
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1076
1492
|
const provider = options.provider;
|
|
1077
1493
|
const method = (options.method ?? "POST").toUpperCase();
|
|
1078
1494
|
const template = ensureTemplate(provider, options.template);
|
|
@@ -1122,6 +1538,29 @@ async function buildTemplateSendOptions(endpointUrl, options) {
|
|
|
1122
1538
|
const signature = await hmacSign("SHA-256", options.secret, built.body);
|
|
1123
1539
|
headers["linear-signature"] = `sha256=${toHex(signature)}`;
|
|
1124
1540
|
}
|
|
1541
|
+
if (provider === "clerk") {
|
|
1542
|
+
const msgId = `msg_${randomHex(16)}`;
|
|
1543
|
+
const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1544
|
+
const signingInput = `${msgId}.${timestamp}.${built.body}`;
|
|
1545
|
+
const secretBytes = decodeStandardWebhookSecret(options.secret);
|
|
1546
|
+
const signature = await hmacSignRaw("SHA-256", secretBytes, signingInput);
|
|
1547
|
+
const sig = `v1,${toBase64(signature)}`;
|
|
1548
|
+
headers["webhook-id"] = msgId;
|
|
1549
|
+
headers["webhook-timestamp"] = String(timestamp);
|
|
1550
|
+
headers["webhook-signature"] = sig;
|
|
1551
|
+
headers["svix-id"] = msgId;
|
|
1552
|
+
headers["svix-timestamp"] = String(timestamp);
|
|
1553
|
+
headers["svix-signature"] = sig;
|
|
1554
|
+
}
|
|
1555
|
+
if (provider === "vercel") {
|
|
1556
|
+
const signature = await hmacSign("SHA-1", options.secret, built.body);
|
|
1557
|
+
headers["x-vercel-signature"] = toHex(signature);
|
|
1558
|
+
}
|
|
1559
|
+
if (provider === "gitlab") {
|
|
1560
|
+
headers["x-gitlab-token"] = options.secret;
|
|
1561
|
+
const gitlabEvent = template === "merge_request" ? "Merge Request Hook" : "Push Hook";
|
|
1562
|
+
headers["x-gitlab-event"] = gitlabEvent;
|
|
1563
|
+
}
|
|
1125
1564
|
return {
|
|
1126
1565
|
method,
|
|
1127
1566
|
headers: {
|
|
@@ -1467,6 +1906,32 @@ async function verifyLinearSignature(body, signatureHeader, secret) {
|
|
|
1467
1906
|
const expected = toHex(await hmacSign("SHA-256", secret, normalizeBody(body))).toLowerCase();
|
|
1468
1907
|
return timingSafeEqual(match[1].toLowerCase(), expected);
|
|
1469
1908
|
}
|
|
1909
|
+
async function verifyVercelSignature(body, signatureHeader, secret) {
|
|
1910
|
+
requireSecret(secret, "verifyVercelSignature");
|
|
1911
|
+
if (!signatureHeader) {
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
const expected = toHex(await hmacSign("SHA-1", secret, normalizeBody(body))).toLowerCase();
|
|
1915
|
+
return timingSafeEqual(signatureHeader.trim().toLowerCase(), expected);
|
|
1916
|
+
}
|
|
1917
|
+
async function verifyGitLabSignature(_body, tokenHeader, secret) {
|
|
1918
|
+
requireSecret(secret, "verifyGitLabSignature");
|
|
1919
|
+
if (!tokenHeader) {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
return timingSafeEqual(tokenHeader, secret);
|
|
1923
|
+
}
|
|
1924
|
+
async function verifyClerkSignature(body, headers, secret) {
|
|
1925
|
+
const normalized = { ...headers };
|
|
1926
|
+
const svixId = getHeader(headers, "svix-id");
|
|
1927
|
+
const svixTs = getHeader(headers, "svix-timestamp");
|
|
1928
|
+
const svixSig = getHeader(headers, "svix-signature");
|
|
1929
|
+
if (svixId && !getHeader(headers, "webhook-id")) normalized["webhook-id"] = svixId;
|
|
1930
|
+
if (svixTs && !getHeader(headers, "webhook-timestamp")) normalized["webhook-timestamp"] = svixTs;
|
|
1931
|
+
if (svixSig && !getHeader(headers, "webhook-signature"))
|
|
1932
|
+
normalized["webhook-signature"] = svixSig;
|
|
1933
|
+
return verifyStandardWebhookSignature(body, normalized, secret);
|
|
1934
|
+
}
|
|
1470
1935
|
async function verifyDiscordSignature(body, headers, publicKey) {
|
|
1471
1936
|
if (!publicKey || typeof publicKey !== "string") {
|
|
1472
1937
|
throw new Error("verifyDiscordSignature requires a non-empty public key");
|
|
@@ -1573,6 +2038,28 @@ async function verifySignature(request, options) {
|
|
|
1573
2038
|
options.secret
|
|
1574
2039
|
);
|
|
1575
2040
|
}
|
|
2041
|
+
if (options.provider === "clerk") {
|
|
2042
|
+
valid = await verifyClerkSignature(request.body, request.headers, options.secret);
|
|
2043
|
+
}
|
|
2044
|
+
if (options.provider === "vercel") {
|
|
2045
|
+
valid = await verifyVercelSignature(
|
|
2046
|
+
request.body,
|
|
2047
|
+
getHeader(request.headers, "x-vercel-signature"),
|
|
2048
|
+
options.secret
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
if (options.provider === "gitlab") {
|
|
2052
|
+
valid = await verifyGitLabSignature(
|
|
2053
|
+
request.body,
|
|
2054
|
+
getHeader(request.headers, "x-gitlab-token"),
|
|
2055
|
+
options.secret
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
if (options.provider === "sendgrid") {
|
|
2059
|
+
throw new Error(
|
|
2060
|
+
"SendGrid does not use signature verification. SendGrid webhooks are verified via IP allowlisting."
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
1576
2063
|
if (options.provider === "discord") {
|
|
1577
2064
|
valid = await verifyDiscordSignature(request.body, request.headers, options.publicKey);
|
|
1578
2065
|
}
|
|
@@ -2893,6 +3380,26 @@ function isDiscordWebhook(request) {
|
|
|
2893
3380
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2894
3381
|
return keys.includes("x-signature-ed25519") && keys.includes("x-signature-timestamp");
|
|
2895
3382
|
}
|
|
3383
|
+
function isSendGridWebhook(request) {
|
|
3384
|
+
if (!request.body) return false;
|
|
3385
|
+
try {
|
|
3386
|
+
const parsed = JSON.parse(request.body);
|
|
3387
|
+
return Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "object" && parsed[0] !== null && "sg_event_id" in parsed[0];
|
|
3388
|
+
} catch {
|
|
3389
|
+
return false;
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
function isClerkWebhook(request) {
|
|
3393
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "svix-id");
|
|
3394
|
+
}
|
|
3395
|
+
function isVercelWebhook(request) {
|
|
3396
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "x-vercel-signature");
|
|
3397
|
+
}
|
|
3398
|
+
function isGitLabWebhook(request) {
|
|
3399
|
+
return Object.keys(request.headers).some(
|
|
3400
|
+
(k) => k.toLowerCase() === "x-gitlab-event" || k.toLowerCase() === "x-gitlab-token"
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
2896
3403
|
function isStandardWebhook(request) {
|
|
2897
3404
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2898
3405
|
return keys.includes("webhook-id") && keys.includes("webhook-timestamp") && keys.includes("webhook-signature");
|
|
@@ -3183,15 +3690,19 @@ function diffRequests(left, right, options = {}) {
|
|
|
3183
3690
|
WebhooksCCError,
|
|
3184
3691
|
diffRequests,
|
|
3185
3692
|
extractJsonField,
|
|
3693
|
+
isClerkWebhook,
|
|
3186
3694
|
isDiscordWebhook,
|
|
3187
3695
|
isGitHubWebhook,
|
|
3696
|
+
isGitLabWebhook,
|
|
3188
3697
|
isLinearWebhook,
|
|
3189
3698
|
isPaddleWebhook,
|
|
3699
|
+
isSendGridWebhook,
|
|
3190
3700
|
isShopifyWebhook,
|
|
3191
3701
|
isSlackWebhook,
|
|
3192
3702
|
isStandardWebhook,
|
|
3193
3703
|
isStripeWebhook,
|
|
3194
3704
|
isTwilioWebhook,
|
|
3705
|
+
isVercelWebhook,
|
|
3195
3706
|
matchAll,
|
|
3196
3707
|
matchAny,
|
|
3197
3708
|
matchBodyPath,
|
|
@@ -3207,8 +3718,10 @@ function diffRequests(left, right, options = {}) {
|
|
|
3207
3718
|
parseFormBody,
|
|
3208
3719
|
parseJsonBody,
|
|
3209
3720
|
parseSSE,
|
|
3721
|
+
verifyClerkSignature,
|
|
3210
3722
|
verifyDiscordSignature,
|
|
3211
3723
|
verifyGitHubSignature,
|
|
3724
|
+
verifyGitLabSignature,
|
|
3212
3725
|
verifyLinearSignature,
|
|
3213
3726
|
verifyPaddleSignature,
|
|
3214
3727
|
verifyShopifySignature,
|
|
@@ -3216,5 +3729,6 @@ function diffRequests(left, right, options = {}) {
|
|
|
3216
3729
|
verifySlackSignature,
|
|
3217
3730
|
verifyStandardWebhookSignature,
|
|
3218
3731
|
verifyStripeSignature,
|
|
3219
|
-
verifyTwilioSignature
|
|
3732
|
+
verifyTwilioSignature,
|
|
3733
|
+
verifyVercelSignature
|
|
3220
3734
|
});
|