@webhooks-cc/sdk 1.0.1 → 1.2.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-DbTsOT_n.d.mts} +14 -3
- package/dist/{diff-BlDnipA4.d.ts → diff-DbTsOT_n.d.ts} +14 -3
- package/dist/index.d.mts +74 -3
- package/dist/index.d.ts +74 -3
- package/dist/index.js +523 -5
- package/dist/index.mjs +515 -4
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +2 -2
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
|
}
|
|
@@ -1851,7 +2331,11 @@ var WebhooksCC = class {
|
|
|
1851
2331
|
return this.request("POST", "/endpoints", body);
|
|
1852
2332
|
},
|
|
1853
2333
|
list: async () => {
|
|
1854
|
-
|
|
2334
|
+
const response = await this.request(
|
|
2335
|
+
"GET",
|
|
2336
|
+
"/endpoints"
|
|
2337
|
+
);
|
|
2338
|
+
return [...response.owned, ...response.shared];
|
|
1855
2339
|
},
|
|
1856
2340
|
get: async (slug) => {
|
|
1857
2341
|
validatePathSegment(slug, "slug");
|
|
@@ -2759,6 +3243,26 @@ function isDiscordWebhook(request) {
|
|
|
2759
3243
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2760
3244
|
return keys.includes("x-signature-ed25519") && keys.includes("x-signature-timestamp");
|
|
2761
3245
|
}
|
|
3246
|
+
function isSendGridWebhook(request) {
|
|
3247
|
+
if (!request.body) return false;
|
|
3248
|
+
try {
|
|
3249
|
+
const parsed = JSON.parse(request.body);
|
|
3250
|
+
return Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "object" && parsed[0] !== null && "sg_event_id" in parsed[0];
|
|
3251
|
+
} catch {
|
|
3252
|
+
return false;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function isClerkWebhook(request) {
|
|
3256
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "svix-id");
|
|
3257
|
+
}
|
|
3258
|
+
function isVercelWebhook(request) {
|
|
3259
|
+
return Object.keys(request.headers).some((k) => k.toLowerCase() === "x-vercel-signature");
|
|
3260
|
+
}
|
|
3261
|
+
function isGitLabWebhook(request) {
|
|
3262
|
+
return Object.keys(request.headers).some(
|
|
3263
|
+
(k) => k.toLowerCase() === "x-gitlab-event" || k.toLowerCase() === "x-gitlab-token"
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
2762
3266
|
function isStandardWebhook(request) {
|
|
2763
3267
|
const keys = Object.keys(request.headers).map((k) => k.toLowerCase());
|
|
2764
3268
|
return keys.includes("webhook-id") && keys.includes("webhook-timestamp") && keys.includes("webhook-signature");
|
|
@@ -2895,15 +3399,19 @@ export {
|
|
|
2895
3399
|
WebhooksCCError,
|
|
2896
3400
|
diffRequests,
|
|
2897
3401
|
extractJsonField,
|
|
3402
|
+
isClerkWebhook,
|
|
2898
3403
|
isDiscordWebhook,
|
|
2899
3404
|
isGitHubWebhook,
|
|
3405
|
+
isGitLabWebhook,
|
|
2900
3406
|
isLinearWebhook,
|
|
2901
3407
|
isPaddleWebhook,
|
|
3408
|
+
isSendGridWebhook,
|
|
2902
3409
|
isShopifyWebhook,
|
|
2903
3410
|
isSlackWebhook,
|
|
2904
3411
|
isStandardWebhook,
|
|
2905
3412
|
isStripeWebhook,
|
|
2906
3413
|
isTwilioWebhook,
|
|
3414
|
+
isVercelWebhook,
|
|
2907
3415
|
matchAll,
|
|
2908
3416
|
matchAny,
|
|
2909
3417
|
matchBodyPath,
|
|
@@ -2919,8 +3427,10 @@ export {
|
|
|
2919
3427
|
parseFormBody,
|
|
2920
3428
|
parseJsonBody,
|
|
2921
3429
|
parseSSE,
|
|
3430
|
+
verifyClerkSignature,
|
|
2922
3431
|
verifyDiscordSignature,
|
|
2923
3432
|
verifyGitHubSignature,
|
|
3433
|
+
verifyGitLabSignature,
|
|
2924
3434
|
verifyLinearSignature,
|
|
2925
3435
|
verifyPaddleSignature,
|
|
2926
3436
|
verifyShopifySignature,
|
|
@@ -2928,5 +3438,6 @@ export {
|
|
|
2928
3438
|
verifySlackSignature,
|
|
2929
3439
|
verifyStandardWebhookSignature,
|
|
2930
3440
|
verifyStripeSignature,
|
|
2931
|
-
verifyTwilioSignature
|
|
3441
|
+
verifyTwilioSignature,
|
|
3442
|
+
verifyVercelSignature
|
|
2932
3443
|
};
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as DiffResult, e as CreateEndpointOptions, R as Request,
|
|
1
|
+
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a1 as WebhooksCC, E as Endpoint } from './diff-DbTsOT_n.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,
|
|
1
|
+
import { g as DiffResult, e as CreateEndpointOptions, R as Request, a1 as WebhooksCC, E as Endpoint } from './diff-DbTsOT_n.js';
|
|
2
2
|
|
|
3
3
|
type TestingClient = Pick<WebhooksCC, "endpoints" | "requests">;
|
|
4
4
|
interface AssertRequestExpectation {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/sdk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "TypeScript SDK for webhooks.cc — create endpoints, capture requests, assert in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"tsup": "^8.5.1",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
|
-
"vitest": "^
|
|
42
|
+
"vitest": "^4.1.1"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsup --config tsup.config.ts",
|