@webhooks-cc/sdk 1.0.0 → 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-Dn4j4B_n.d.mts → diff-C6vrhcEq.d.mts} +4 -2
- package/dist/{diff-Dn4j4B_n.d.ts → diff-C6vrhcEq.d.ts} +4 -2
- package/dist/index.d.mts +74 -3
- package/dist/index.d.ts +74 -3
- package/dist/index.js +522 -5
- package/dist/index.mjs +514 -4
- 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
|
}
|
|
@@ -1815,10 +2295,13 @@ async function collectMatchingRequests(fetchRequests, options) {
|
|
|
1815
2295
|
throw new TimeoutError(timeout);
|
|
1816
2296
|
}
|
|
1817
2297
|
function validateMockResponse(mockResponse, fieldName) {
|
|
1818
|
-
const { status } = mockResponse;
|
|
2298
|
+
const { status, delay } = mockResponse;
|
|
1819
2299
|
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
1820
2300
|
throw new Error(`Invalid ${fieldName} status: ${status}. Must be an integer 100-599.`);
|
|
1821
2301
|
}
|
|
2302
|
+
if (delay !== void 0 && (!Number.isInteger(delay) || delay < 0 || delay > 3e4)) {
|
|
2303
|
+
throw new Error(`Invalid ${fieldName} delay: ${delay}. Must be an integer 0-30000.`);
|
|
2304
|
+
}
|
|
1822
2305
|
}
|
|
1823
2306
|
var WebhooksCC = class {
|
|
1824
2307
|
constructor(options) {
|
|
@@ -2756,6 +3239,26 @@ function isDiscordWebhook(request) {
|
|
|
2756
3239
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2757
3240
|
return keys.includes("x-signature-ed25519") && keys.includes("x-signature-timestamp");
|
|
2758
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
|
+
}
|
|
2759
3262
|
function isStandardWebhook(request) {
|
|
2760
3263
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2761
3264
|
return keys.includes("webhook-id") && keys.includes("webhook-timestamp") && keys.includes("webhook-signature");
|
|
@@ -2892,15 +3395,19 @@ export {
|
|
|
2892
3395
|
WebhooksCCError,
|
|
2893
3396
|
diffRequests,
|
|
2894
3397
|
extractJsonField,
|
|
3398
|
+
isClerkWebhook,
|
|
2895
3399
|
isDiscordWebhook,
|
|
2896
3400
|
isGitHubWebhook,
|
|
3401
|
+
isGitLabWebhook,
|
|
2897
3402
|
isLinearWebhook,
|
|
2898
3403
|
isPaddleWebhook,
|
|
3404
|
+
isSendGridWebhook,
|
|
2899
3405
|
isShopifyWebhook,
|
|
2900
3406
|
isSlackWebhook,
|
|
2901
3407
|
isStandardWebhook,
|
|
2902
3408
|
isStripeWebhook,
|
|
2903
3409
|
isTwilioWebhook,
|
|
3410
|
+
isVercelWebhook,
|
|
2904
3411
|
matchAll,
|
|
2905
3412
|
matchAny,
|
|
2906
3413
|
matchBodyPath,
|
|
@@ -2916,8 +3423,10 @@ export {
|
|
|
2916
3423
|
parseFormBody,
|
|
2917
3424
|
parseJsonBody,
|
|
2918
3425
|
parseSSE,
|
|
3426
|
+
verifyClerkSignature,
|
|
2919
3427
|
verifyDiscordSignature,
|
|
2920
3428
|
verifyGitHubSignature,
|
|
3429
|
+
verifyGitLabSignature,
|
|
2921
3430
|
verifyLinearSignature,
|
|
2922
3431
|
verifyPaddleSignature,
|
|
2923
3432
|
verifyShopifySignature,
|
|
@@ -2925,5 +3434,6 @@ export {
|
|
|
2925
3434
|
verifySlackSignature,
|
|
2926
3435
|
verifyStandardWebhookSignature,
|
|
2927
3436
|
verifyStripeSignature,
|
|
2928
|
-
verifyTwilioSignature
|
|
3437
|
+
verifyTwilioSignature,
|
|
3438
|
+
verifyVercelSignature
|
|
2929
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 {
|