@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/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
  };
@@ -1,4 +1,4 @@
1
- import { g as DiffResult, e as CreateEndpointOptions, R as Request, a0 as WebhooksCC, E as Endpoint } from './diff-Dn4j4B_n.mjs';
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-Dn4j4B_n.js';
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.1.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",