@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/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
- return this.request("GET", "/endpoints");
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
  };
@@ -1,4 +1,4 @@
1
- import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-BlDnipA4.mjs';
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, a0 as WebhooksCC, E as Endpoint } from './diff-BlDnipA4.js';
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.1",
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": "^3.2.4"
42
+ "vitest": "^4.1.1"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup --config tsup.config.ts",