@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.mjs
CHANGED
|
@@ -91,7 +91,12 @@ var DEFAULT_TEMPLATE_BY_PROVIDER = {
|
|
|
91
91
|
twilio: "messaging.inbound",
|
|
92
92
|
slack: "event_callback",
|
|
93
93
|
paddle: "transaction.completed",
|
|
94
|
-
linear: "issue.create"
|
|
94
|
+
linear: "issue.create",
|
|
95
|
+
sendgrid: "delivered",
|
|
96
|
+
clerk: "user.created",
|
|
97
|
+
discord: "interaction_create",
|
|
98
|
+
vercel: "deployment.created",
|
|
99
|
+
gitlab: "push"
|
|
95
100
|
};
|
|
96
101
|
var PROVIDER_TEMPLATES = {
|
|
97
102
|
stripe: ["payment_intent.succeeded", "checkout.session.completed", "invoice.paid"],
|
|
@@ -100,7 +105,12 @@ var PROVIDER_TEMPLATES = {
|
|
|
100
105
|
twilio: ["messaging.inbound", "messaging.status_callback", "voice.incoming_call"],
|
|
101
106
|
slack: ["event_callback", "slash_command", "url_verification"],
|
|
102
107
|
paddle: ["transaction.completed", "subscription.created", "subscription.updated"],
|
|
103
|
-
linear: ["issue.create", "issue.update", "comment.create"]
|
|
108
|
+
linear: ["issue.create", "issue.update", "comment.create"],
|
|
109
|
+
sendgrid: ["delivered", "open", "bounce", "spam_report"],
|
|
110
|
+
clerk: ["user.created", "user.updated", "user.deleted", "session.created"],
|
|
111
|
+
discord: ["interaction_create", "message_component", "ping"],
|
|
112
|
+
vercel: ["deployment.created", "deployment.succeeded", "deployment.error"],
|
|
113
|
+
gitlab: ["push", "merge_request"]
|
|
104
114
|
};
|
|
105
115
|
var TEMPLATE_PROVIDERS = [
|
|
106
116
|
"stripe",
|
|
@@ -110,6 +120,11 @@ var TEMPLATE_PROVIDERS = [
|
|
|
110
120
|
"slack",
|
|
111
121
|
"paddle",
|
|
112
122
|
"linear",
|
|
123
|
+
"sendgrid",
|
|
124
|
+
"clerk",
|
|
125
|
+
"discord",
|
|
126
|
+
"vercel",
|
|
127
|
+
"gitlab",
|
|
113
128
|
"standard-webhooks"
|
|
114
129
|
];
|
|
115
130
|
var TEMPLATE_METADATA = Object.freeze({
|
|
@@ -169,6 +184,42 @@ var TEMPLATE_METADATA = Object.freeze({
|
|
|
169
184
|
signatureHeader: "linear-signature",
|
|
170
185
|
signatureAlgorithm: "hmac-sha256"
|
|
171
186
|
}),
|
|
187
|
+
sendgrid: Object.freeze({
|
|
188
|
+
provider: "sendgrid",
|
|
189
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.sendgrid]),
|
|
190
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.sendgrid,
|
|
191
|
+
secretRequired: false
|
|
192
|
+
}),
|
|
193
|
+
clerk: Object.freeze({
|
|
194
|
+
provider: "clerk",
|
|
195
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.clerk]),
|
|
196
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.clerk,
|
|
197
|
+
secretRequired: true,
|
|
198
|
+
signatureHeader: "webhook-signature",
|
|
199
|
+
signatureAlgorithm: "hmac-sha256"
|
|
200
|
+
}),
|
|
201
|
+
discord: Object.freeze({
|
|
202
|
+
provider: "discord",
|
|
203
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.discord]),
|
|
204
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.discord,
|
|
205
|
+
secretRequired: false
|
|
206
|
+
}),
|
|
207
|
+
vercel: Object.freeze({
|
|
208
|
+
provider: "vercel",
|
|
209
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.vercel]),
|
|
210
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.vercel,
|
|
211
|
+
secretRequired: true,
|
|
212
|
+
signatureHeader: "x-vercel-signature",
|
|
213
|
+
signatureAlgorithm: "hmac-sha1"
|
|
214
|
+
}),
|
|
215
|
+
gitlab: Object.freeze({
|
|
216
|
+
provider: "gitlab",
|
|
217
|
+
templates: Object.freeze([...PROVIDER_TEMPLATES.gitlab]),
|
|
218
|
+
defaultTemplate: DEFAULT_TEMPLATE_BY_PROVIDER.gitlab,
|
|
219
|
+
secretRequired: true,
|
|
220
|
+
signatureHeader: "x-gitlab-token",
|
|
221
|
+
signatureAlgorithm: "token"
|
|
222
|
+
}),
|
|
172
223
|
"standard-webhooks": Object.freeze({
|
|
173
224
|
provider: "standard-webhooks",
|
|
174
225
|
templates: Object.freeze([]),
|
|
@@ -754,6 +805,228 @@ function buildTemplatePayload(provider, template, event, now, bodyOverride) {
|
|
|
754
805
|
}
|
|
755
806
|
};
|
|
756
807
|
}
|
|
808
|
+
if (provider === "clerk") {
|
|
809
|
+
const userId = `user_${randomHex(24)}`;
|
|
810
|
+
const payloadByTemplate = {
|
|
811
|
+
"user.created": {
|
|
812
|
+
data: {
|
|
813
|
+
id: userId,
|
|
814
|
+
object: "user",
|
|
815
|
+
email_addresses: [
|
|
816
|
+
{
|
|
817
|
+
id: `idn_${randomHex(24)}`,
|
|
818
|
+
email_address: "user@example.com",
|
|
819
|
+
verification: { status: "verified", strategy: "email_code" }
|
|
820
|
+
}
|
|
821
|
+
],
|
|
822
|
+
first_name: "Jane",
|
|
823
|
+
last_name: "Doe",
|
|
824
|
+
created_at: nowSec * 1e3,
|
|
825
|
+
updated_at: nowSec * 1e3
|
|
826
|
+
},
|
|
827
|
+
object: "event",
|
|
828
|
+
type: "user.created",
|
|
829
|
+
timestamp: nowSec * 1e3
|
|
830
|
+
},
|
|
831
|
+
"user.updated": {
|
|
832
|
+
data: {
|
|
833
|
+
id: userId,
|
|
834
|
+
object: "user",
|
|
835
|
+
email_addresses: [
|
|
836
|
+
{
|
|
837
|
+
id: `idn_${randomHex(24)}`,
|
|
838
|
+
email_address: "user@example.com",
|
|
839
|
+
verification: { status: "verified", strategy: "email_code" }
|
|
840
|
+
}
|
|
841
|
+
],
|
|
842
|
+
first_name: "Jane",
|
|
843
|
+
last_name: "Smith",
|
|
844
|
+
created_at: nowSec * 1e3,
|
|
845
|
+
updated_at: nowSec * 1e3
|
|
846
|
+
},
|
|
847
|
+
object: "event",
|
|
848
|
+
type: "user.updated",
|
|
849
|
+
timestamp: nowSec * 1e3
|
|
850
|
+
},
|
|
851
|
+
"user.deleted": {
|
|
852
|
+
data: {
|
|
853
|
+
id: userId,
|
|
854
|
+
object: "user",
|
|
855
|
+
deleted: true
|
|
856
|
+
},
|
|
857
|
+
object: "event",
|
|
858
|
+
type: "user.deleted",
|
|
859
|
+
timestamp: nowSec * 1e3
|
|
860
|
+
},
|
|
861
|
+
"session.created": {
|
|
862
|
+
data: {
|
|
863
|
+
id: `sess_${randomHex(24)}`,
|
|
864
|
+
object: "session",
|
|
865
|
+
user_id: userId,
|
|
866
|
+
status: "active",
|
|
867
|
+
created_at: nowSec * 1e3,
|
|
868
|
+
updated_at: nowSec * 1e3,
|
|
869
|
+
expire_at: (nowSec + 86400) * 1e3
|
|
870
|
+
},
|
|
871
|
+
object: "event",
|
|
872
|
+
type: "session.created",
|
|
873
|
+
timestamp: nowSec * 1e3
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
877
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
878
|
+
return {
|
|
879
|
+
body,
|
|
880
|
+
contentType: "application/json",
|
|
881
|
+
headers: {
|
|
882
|
+
"user-agent": "Svix-Webhooks/1.0"
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
if (provider === "vercel") {
|
|
887
|
+
const deploymentId = `dpl_${randomHex(20)}`;
|
|
888
|
+
const projectId = `prj_${randomHex(20)}`;
|
|
889
|
+
const teamId = `team_${randomHex(20)}`;
|
|
890
|
+
const payloadByTemplate = {
|
|
891
|
+
"deployment.created": {
|
|
892
|
+
id: randomUuid(),
|
|
893
|
+
type: "deployment.created",
|
|
894
|
+
createdAt: nowSec * 1e3,
|
|
895
|
+
payload: {
|
|
896
|
+
deployment: {
|
|
897
|
+
id: deploymentId,
|
|
898
|
+
name: "webhooks-cc-web",
|
|
899
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
900
|
+
meta: {
|
|
901
|
+
githubCommitRef: "main",
|
|
902
|
+
githubCommitSha: randomHex(40),
|
|
903
|
+
githubCommitMessage: "Update webhook templates"
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
907
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
"deployment.succeeded": {
|
|
911
|
+
id: randomUuid(),
|
|
912
|
+
type: "deployment.succeeded",
|
|
913
|
+
createdAt: nowSec * 1e3,
|
|
914
|
+
payload: {
|
|
915
|
+
deployment: {
|
|
916
|
+
id: deploymentId,
|
|
917
|
+
name: "webhooks-cc-web",
|
|
918
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
919
|
+
readyState: "READY"
|
|
920
|
+
},
|
|
921
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
922
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
"deployment.error": {
|
|
926
|
+
id: randomUuid(),
|
|
927
|
+
type: "deployment.error",
|
|
928
|
+
createdAt: nowSec * 1e3,
|
|
929
|
+
payload: {
|
|
930
|
+
deployment: {
|
|
931
|
+
id: deploymentId,
|
|
932
|
+
name: "webhooks-cc-web",
|
|
933
|
+
url: `webhooks-cc-web-${randomHex(8)}.vercel.app`,
|
|
934
|
+
readyState: "ERROR",
|
|
935
|
+
errorMessage: "Build failed: exit code 1"
|
|
936
|
+
},
|
|
937
|
+
project: { id: projectId, name: "webhooks-cc-web" },
|
|
938
|
+
team: { id: teamId, name: "webhooks-cc" }
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
943
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
944
|
+
return {
|
|
945
|
+
body,
|
|
946
|
+
contentType: "application/json",
|
|
947
|
+
headers: {
|
|
948
|
+
"user-agent": "Vercel/1.0"
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (provider === "gitlab") {
|
|
953
|
+
const projectId = Number(randomDigits(7));
|
|
954
|
+
const payloadByTemplate = {
|
|
955
|
+
push: {
|
|
956
|
+
object_kind: "push",
|
|
957
|
+
event_name: "push",
|
|
958
|
+
before: randomHex(40),
|
|
959
|
+
after: randomHex(40),
|
|
960
|
+
ref: "refs/heads/main",
|
|
961
|
+
checkout_sha: randomHex(40),
|
|
962
|
+
user_id: Number(randomDigits(5)),
|
|
963
|
+
user_name: "webhooks-cc-bot",
|
|
964
|
+
user_email: "bot@webhooks.cc",
|
|
965
|
+
project_id: projectId,
|
|
966
|
+
project: {
|
|
967
|
+
id: projectId,
|
|
968
|
+
name: "demo-repo",
|
|
969
|
+
web_url: "https://gitlab.com/webhooks-cc/demo-repo",
|
|
970
|
+
namespace: "webhooks-cc",
|
|
971
|
+
default_branch: "main"
|
|
972
|
+
},
|
|
973
|
+
commits: [
|
|
974
|
+
{
|
|
975
|
+
id: randomHex(40),
|
|
976
|
+
message: "Update webhook integration tests",
|
|
977
|
+
title: "Update webhook integration tests",
|
|
978
|
+
timestamp: nowIso,
|
|
979
|
+
url: `https://gitlab.com/webhooks-cc/demo-repo/-/commit/${randomHex(40)}`,
|
|
980
|
+
author: { name: "webhooks-cc-bot", email: "bot@webhooks.cc" },
|
|
981
|
+
added: [],
|
|
982
|
+
modified: ["src/webhooks.ts"],
|
|
983
|
+
removed: []
|
|
984
|
+
}
|
|
985
|
+
],
|
|
986
|
+
total_commits_count: 1
|
|
987
|
+
},
|
|
988
|
+
merge_request: {
|
|
989
|
+
object_kind: "merge_request",
|
|
990
|
+
event_type: "merge_request",
|
|
991
|
+
user: {
|
|
992
|
+
id: Number(randomDigits(5)),
|
|
993
|
+
name: "webhooks-cc-bot",
|
|
994
|
+
username: "webhooks-cc-bot",
|
|
995
|
+
email: "bot@webhooks.cc"
|
|
996
|
+
},
|
|
997
|
+
project: {
|
|
998
|
+
id: projectId,
|
|
999
|
+
name: "demo-repo",
|
|
1000
|
+
web_url: "https://gitlab.com/webhooks-cc/demo-repo",
|
|
1001
|
+
namespace: "webhooks-cc",
|
|
1002
|
+
default_branch: "main"
|
|
1003
|
+
},
|
|
1004
|
+
object_attributes: {
|
|
1005
|
+
id: Number(randomDigits(7)),
|
|
1006
|
+
iid: 42,
|
|
1007
|
+
title: "Add webhook retry logic",
|
|
1008
|
+
description: "This MR improves retry handling for inbound webhooks.",
|
|
1009
|
+
state: "opened",
|
|
1010
|
+
action: "open",
|
|
1011
|
+
source_branch: "feature/webhook-retries",
|
|
1012
|
+
target_branch: "main",
|
|
1013
|
+
created_at: nowIso,
|
|
1014
|
+
updated_at: nowIso,
|
|
1015
|
+
url: `https://gitlab.com/webhooks-cc/demo-repo/-/merge_requests/42`
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
const payload = bodyOverride ?? payloadByTemplate[template];
|
|
1020
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1021
|
+
return {
|
|
1022
|
+
body,
|
|
1023
|
+
contentType: "application/json",
|
|
1024
|
+
headers: {
|
|
1025
|
+
"x-gitlab-event": template === "merge_request" ? "Merge Request Hook" : "Push Hook",
|
|
1026
|
+
"user-agent": "GitLab/1.0"
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
757
1030
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
758
1031
|
}
|
|
759
1032
|
const defaultTwilioParamsByTemplate = {
|
|
@@ -939,6 +1212,142 @@ async function buildTemplateSendOptions(endpointUrl, options) {
|
|
|
939
1212
|
body
|
|
940
1213
|
};
|
|
941
1214
|
}
|
|
1215
|
+
if (options.provider === "sendgrid") {
|
|
1216
|
+
const method2 = (options.method ?? "POST").toUpperCase();
|
|
1217
|
+
const supported = PROVIDER_TEMPLATES.sendgrid;
|
|
1218
|
+
const template2 = options.template ?? DEFAULT_TEMPLATE_BY_PROVIDER.sendgrid;
|
|
1219
|
+
if (!supported.some((item) => item === template2)) {
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
`Unsupported template "${template2}" for provider "sendgrid". Supported templates: ${supported.join(", ")}`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1225
|
+
const payloadByTemplate = {
|
|
1226
|
+
delivered: [
|
|
1227
|
+
{
|
|
1228
|
+
sg_event_id: randomHex(22),
|
|
1229
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1230
|
+
email: "recipient@example.com",
|
|
1231
|
+
timestamp: nowSec,
|
|
1232
|
+
event: "delivered",
|
|
1233
|
+
smtp_id: `<${randomHex(20)}@example.com>`,
|
|
1234
|
+
ip: "168.1.1.1",
|
|
1235
|
+
response: "250 OK",
|
|
1236
|
+
category: ["webhooks-cc-test"]
|
|
1237
|
+
}
|
|
1238
|
+
],
|
|
1239
|
+
open: [
|
|
1240
|
+
{
|
|
1241
|
+
sg_event_id: randomHex(22),
|
|
1242
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1243
|
+
email: "recipient@example.com",
|
|
1244
|
+
timestamp: nowSec,
|
|
1245
|
+
event: "open",
|
|
1246
|
+
ip: "72.14.199.28",
|
|
1247
|
+
useragent: "Mozilla/5.0",
|
|
1248
|
+
category: ["webhooks-cc-test"]
|
|
1249
|
+
}
|
|
1250
|
+
],
|
|
1251
|
+
bounce: [
|
|
1252
|
+
{
|
|
1253
|
+
sg_event_id: randomHex(22),
|
|
1254
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1255
|
+
email: "bounced@example.com",
|
|
1256
|
+
timestamp: nowSec,
|
|
1257
|
+
event: "bounce",
|
|
1258
|
+
type: "bounce",
|
|
1259
|
+
status: "5.1.1",
|
|
1260
|
+
reason: "550 5.1.1 The email account does not exist.",
|
|
1261
|
+
ip: "168.1.1.1",
|
|
1262
|
+
category: ["webhooks-cc-test"]
|
|
1263
|
+
}
|
|
1264
|
+
],
|
|
1265
|
+
spam_report: [
|
|
1266
|
+
{
|
|
1267
|
+
sg_event_id: randomHex(22),
|
|
1268
|
+
sg_message_id: `${randomHex(20)}.${randomDigits(4)}`,
|
|
1269
|
+
email: "complainant@example.com",
|
|
1270
|
+
timestamp: nowSec,
|
|
1271
|
+
event: "spamreport",
|
|
1272
|
+
category: ["webhooks-cc-test"]
|
|
1273
|
+
}
|
|
1274
|
+
]
|
|
1275
|
+
};
|
|
1276
|
+
const payload = options.body ?? payloadByTemplate[template2];
|
|
1277
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1278
|
+
return {
|
|
1279
|
+
method: method2,
|
|
1280
|
+
headers: {
|
|
1281
|
+
"content-type": "application/json",
|
|
1282
|
+
"user-agent": "SendGrid/1.0",
|
|
1283
|
+
"x-webhooks-cc-template-provider": "sendgrid",
|
|
1284
|
+
"x-webhooks-cc-template-template": template2,
|
|
1285
|
+
"x-webhooks-cc-template-event": template2,
|
|
1286
|
+
...options.headers ?? {}
|
|
1287
|
+
},
|
|
1288
|
+
body
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
if (options.provider === "discord") {
|
|
1292
|
+
const method2 = (options.method ?? "POST").toUpperCase();
|
|
1293
|
+
const supported = PROVIDER_TEMPLATES.discord;
|
|
1294
|
+
const template2 = options.template ?? DEFAULT_TEMPLATE_BY_PROVIDER.discord;
|
|
1295
|
+
if (!supported.some((item) => item === template2)) {
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
`Unsupported template "${template2}" for provider "discord". Supported templates: ${supported.join(", ")}`
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1301
|
+
const payloadByTemplate = {
|
|
1302
|
+
interaction_create: {
|
|
1303
|
+
id: randomHex(18),
|
|
1304
|
+
application_id: randomHex(18),
|
|
1305
|
+
type: 2,
|
|
1306
|
+
data: {
|
|
1307
|
+
id: randomHex(18),
|
|
1308
|
+
name: "webhook-test",
|
|
1309
|
+
type: 1
|
|
1310
|
+
},
|
|
1311
|
+
guild_id: randomHex(18),
|
|
1312
|
+
channel_id: randomHex(18),
|
|
1313
|
+
token: randomHex(40),
|
|
1314
|
+
version: 1
|
|
1315
|
+
},
|
|
1316
|
+
message_component: {
|
|
1317
|
+
id: randomHex(18),
|
|
1318
|
+
application_id: randomHex(18),
|
|
1319
|
+
type: 3,
|
|
1320
|
+
data: {
|
|
1321
|
+
custom_id: "click_me",
|
|
1322
|
+
component_type: 2
|
|
1323
|
+
},
|
|
1324
|
+
guild_id: randomHex(18),
|
|
1325
|
+
channel_id: randomHex(18),
|
|
1326
|
+
token: randomHex(40),
|
|
1327
|
+
version: 1
|
|
1328
|
+
},
|
|
1329
|
+
ping: {
|
|
1330
|
+
id: randomHex(18),
|
|
1331
|
+
application_id: randomHex(18),
|
|
1332
|
+
type: 1,
|
|
1333
|
+
version: 1
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
const payload = options.body ?? payloadByTemplate[template2];
|
|
1337
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1338
|
+
return {
|
|
1339
|
+
method: method2,
|
|
1340
|
+
headers: {
|
|
1341
|
+
"content-type": "application/json",
|
|
1342
|
+
"x-signature-timestamp": String(timestamp),
|
|
1343
|
+
"x-webhooks-cc-template-provider": "discord",
|
|
1344
|
+
"x-webhooks-cc-template-template": template2,
|
|
1345
|
+
"x-webhooks-cc-template-event": template2,
|
|
1346
|
+
...options.headers ?? {}
|
|
1347
|
+
},
|
|
1348
|
+
body
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
942
1351
|
const provider = options.provider;
|
|
943
1352
|
const method = (options.method ?? "POST").toUpperCase();
|
|
944
1353
|
const template = ensureTemplate(provider, options.template);
|
|
@@ -988,6 +1397,29 @@ async function buildTemplateSendOptions(endpointUrl, options) {
|
|
|
988
1397
|
const signature = await hmacSign("SHA-256", options.secret, built.body);
|
|
989
1398
|
headers["linear-signature"] = `sha256=${toHex(signature)}`;
|
|
990
1399
|
}
|
|
1400
|
+
if (provider === "clerk") {
|
|
1401
|
+
const msgId = `msg_${randomHex(16)}`;
|
|
1402
|
+
const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1403
|
+
const signingInput = `${msgId}.${timestamp}.${built.body}`;
|
|
1404
|
+
const secretBytes = decodeStandardWebhookSecret(options.secret);
|
|
1405
|
+
const signature = await hmacSignRaw("SHA-256", secretBytes, signingInput);
|
|
1406
|
+
const sig = `v1,${toBase64(signature)}`;
|
|
1407
|
+
headers["webhook-id"] = msgId;
|
|
1408
|
+
headers["webhook-timestamp"] = String(timestamp);
|
|
1409
|
+
headers["webhook-signature"] = sig;
|
|
1410
|
+
headers["svix-id"] = msgId;
|
|
1411
|
+
headers["svix-timestamp"] = String(timestamp);
|
|
1412
|
+
headers["svix-signature"] = sig;
|
|
1413
|
+
}
|
|
1414
|
+
if (provider === "vercel") {
|
|
1415
|
+
const signature = await hmacSign("SHA-1", options.secret, built.body);
|
|
1416
|
+
headers["x-vercel-signature"] = toHex(signature);
|
|
1417
|
+
}
|
|
1418
|
+
if (provider === "gitlab") {
|
|
1419
|
+
headers["x-gitlab-token"] = options.secret;
|
|
1420
|
+
const gitlabEvent = template === "merge_request" ? "Merge Request Hook" : "Push Hook";
|
|
1421
|
+
headers["x-gitlab-event"] = gitlabEvent;
|
|
1422
|
+
}
|
|
991
1423
|
return {
|
|
992
1424
|
method,
|
|
993
1425
|
headers: {
|
|
@@ -1333,6 +1765,32 @@ async function verifyLinearSignature(body, signatureHeader, secret) {
|
|
|
1333
1765
|
const expected = toHex(await hmacSign("SHA-256", secret, normalizeBody(body))).toLowerCase();
|
|
1334
1766
|
return timingSafeEqual(match[1].toLowerCase(), expected);
|
|
1335
1767
|
}
|
|
1768
|
+
async function verifyVercelSignature(body, signatureHeader, secret) {
|
|
1769
|
+
requireSecret(secret, "verifyVercelSignature");
|
|
1770
|
+
if (!signatureHeader) {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
const expected = toHex(await hmacSign("SHA-1", secret, normalizeBody(body))).toLowerCase();
|
|
1774
|
+
return timingSafeEqual(signatureHeader.trim().toLowerCase(), expected);
|
|
1775
|
+
}
|
|
1776
|
+
async function verifyGitLabSignature(_body, tokenHeader, secret) {
|
|
1777
|
+
requireSecret(secret, "verifyGitLabSignature");
|
|
1778
|
+
if (!tokenHeader) {
|
|
1779
|
+
return false;
|
|
1780
|
+
}
|
|
1781
|
+
return timingSafeEqual(tokenHeader, secret);
|
|
1782
|
+
}
|
|
1783
|
+
async function verifyClerkSignature(body, headers, secret) {
|
|
1784
|
+
const normalized = { ...headers };
|
|
1785
|
+
const svixId = getHeader(headers, "svix-id");
|
|
1786
|
+
const svixTs = getHeader(headers, "svix-timestamp");
|
|
1787
|
+
const svixSig = getHeader(headers, "svix-signature");
|
|
1788
|
+
if (svixId && !getHeader(headers, "webhook-id")) normalized["webhook-id"] = svixId;
|
|
1789
|
+
if (svixTs && !getHeader(headers, "webhook-timestamp")) normalized["webhook-timestamp"] = svixTs;
|
|
1790
|
+
if (svixSig && !getHeader(headers, "webhook-signature"))
|
|
1791
|
+
normalized["webhook-signature"] = svixSig;
|
|
1792
|
+
return verifyStandardWebhookSignature(body, normalized, secret);
|
|
1793
|
+
}
|
|
1336
1794
|
async function verifyDiscordSignature(body, headers, publicKey) {
|
|
1337
1795
|
if (!publicKey || typeof publicKey !== "string") {
|
|
1338
1796
|
throw new Error("verifyDiscordSignature requires a non-empty public key");
|
|
@@ -1439,6 +1897,28 @@ async function verifySignature(request, options) {
|
|
|
1439
1897
|
options.secret
|
|
1440
1898
|
);
|
|
1441
1899
|
}
|
|
1900
|
+
if (options.provider === "clerk") {
|
|
1901
|
+
valid = await verifyClerkSignature(request.body, request.headers, options.secret);
|
|
1902
|
+
}
|
|
1903
|
+
if (options.provider === "vercel") {
|
|
1904
|
+
valid = await verifyVercelSignature(
|
|
1905
|
+
request.body,
|
|
1906
|
+
getHeader(request.headers, "x-vercel-signature"),
|
|
1907
|
+
options.secret
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
if (options.provider === "gitlab") {
|
|
1911
|
+
valid = await verifyGitLabSignature(
|
|
1912
|
+
request.body,
|
|
1913
|
+
getHeader(request.headers, "x-gitlab-token"),
|
|
1914
|
+
options.secret
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
if (options.provider === "sendgrid") {
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
"SendGrid does not use signature verification. SendGrid webhooks are verified via IP allowlisting."
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1442
1922
|
if (options.provider === "discord") {
|
|
1443
1923
|
valid = await verifyDiscordSignature(request.body, request.headers, options.publicKey);
|
|
1444
1924
|
}
|
|
@@ -2759,6 +3239,26 @@ function isDiscordWebhook(request) {
|
|
|
2759
3239
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2760
3240
|
return keys.includes("x-signature-ed25519") && keys.includes("x-signature-timestamp");
|
|
2761
3241
|
}
|
|
3242
|
+
function isSendGridWebhook(request) {
|
|
3243
|
+
if (!request.body) return false;
|
|
3244
|
+
try {
|
|
3245
|
+
const parsed = JSON.parse(request.body);
|
|
3246
|
+
return Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "object" && parsed[0] !== null && "sg_event_id" in parsed[0];
|
|
3247
|
+
} catch {
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
function isClerkWebhook(request) {
|
|
3252
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "svix-id");
|
|
3253
|
+
}
|
|
3254
|
+
function isVercelWebhook(request) {
|
|
3255
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "x-vercel-signature");
|
|
3256
|
+
}
|
|
3257
|
+
function isGitLabWebhook(request) {
|
|
3258
|
+
return Object.keys(request.headers).some(
|
|
3259
|
+
(k) => k.toLowerCase() === "x-gitlab-event" || k.toLowerCase() === "x-gitlab-token"
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
2762
3262
|
function isStandardWebhook(request) {
|
|
2763
3263
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2764
3264
|
return keys.includes("webhook-id") && keys.includes("webhook-timestamp") && keys.includes("webhook-signature");
|
|
@@ -2895,15 +3395,19 @@ export {
|
|
|
2895
3395
|
WebhooksCCError,
|
|
2896
3396
|
diffRequests,
|
|
2897
3397
|
extractJsonField,
|
|
3398
|
+
isClerkWebhook,
|
|
2898
3399
|
isDiscordWebhook,
|
|
2899
3400
|
isGitHubWebhook,
|
|
3401
|
+
isGitLabWebhook,
|
|
2900
3402
|
isLinearWebhook,
|
|
2901
3403
|
isPaddleWebhook,
|
|
3404
|
+
isSendGridWebhook,
|
|
2902
3405
|
isShopifyWebhook,
|
|
2903
3406
|
isSlackWebhook,
|
|
2904
3407
|
isStandardWebhook,
|
|
2905
3408
|
isStripeWebhook,
|
|
2906
3409
|
isTwilioWebhook,
|
|
3410
|
+
isVercelWebhook,
|
|
2907
3411
|
matchAll,
|
|
2908
3412
|
matchAny,
|
|
2909
3413
|
matchBodyPath,
|
|
@@ -2919,8 +3423,10 @@ export {
|
|
|
2919
3423
|
parseFormBody,
|
|
2920
3424
|
parseJsonBody,
|
|
2921
3425
|
parseSSE,
|
|
3426
|
+
verifyClerkSignature,
|
|
2922
3427
|
verifyDiscordSignature,
|
|
2923
3428
|
verifyGitHubSignature,
|
|
3429
|
+
verifyGitLabSignature,
|
|
2924
3430
|
verifyLinearSignature,
|
|
2925
3431
|
verifyPaddleSignature,
|
|
2926
3432
|
verifyShopifySignature,
|
|
@@ -2928,5 +3434,6 @@ export {
|
|
|
2928
3434
|
verifySlackSignature,
|
|
2929
3435
|
verifyStandardWebhookSignature,
|
|
2930
3436
|
verifyStripeSignature,
|
|
2931
|
-
verifyTwilioSignature
|
|
3437
|
+
verifyTwilioSignature,
|
|
3438
|
+
verifyVercelSignature
|
|
2932
3439
|
};
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-
|
|
1
|
+
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-C6vrhcEq.mjs';
|
|
2
2
|
|
|
3
3
|
type TestingClient = Pick<WebhooksCC, "endpoints" | "requests">;
|
|
4
4
|
interface AssertRequestExpectation {
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-
|
|
1
|
+
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-C6vrhcEq.js';
|
|
2
2
|
|
|
3
3
|
type TestingClient = Pick<WebhooksCC, "endpoints" | "requests">;
|
|
4
4
|
interface AssertRequestExpectation {
|