Repository: octokit/webhooks.js (ts)

Issue:
Create .onAny and .onError methods, deprecate "*" and "error" events
Follow up to https://github.com/octokit/webhooks.js/issues/180

I would reserve the `.on(event, handler)` method for actual events only, and define new methods for special events that are unique to `@octokit/webhooks`.

That will also address https://github.com/octokit/webhooks.js/issues/302, because we would no longer need to define `*` and `error` as part of the overall event map

Pull request:
feat: Add onAny and onEvent functions
Fixes #304

Patch:
diff --git a/src/event-handler/index.ts b/src/event-handler/index.ts
index 7f2d6e3..25023ea 100644
--- a/src/event-handler/index.ts
+++ b/src/event-handler/index.ts
@@ -1,4 +1,8 @@
-import { receiverOn as on } from "./on";
+import {
+  receiverOn as on,
+  receiverOnAny as onAny,
+  receiverOnError as onError,
+} from "./on";
 import { receiverHandle as receive } from "./receive";
 import { removeListener } from "./remove-listener";
 import { Options, State } from "../types";
@@ -14,6 +18,8 @@ export function createEventHandler(options: Options<any>) {
 
   return {
     on: on.bind(null, state),
+    onAny: onAny.bind(null, state),
+    onError: onError.bind(null, state),
     removeListener: removeListener.bind(null, state),
     receive: receive.bind(null, state),
   };
diff --git a/src/event-handler/on.ts b/src/event-handler/on.ts
index bd3d0d9..cf7e0dd 100644
--- a/src/event-handler/on.ts
+++ b/src/event-handler/on.ts
@@ -1,7 +1,18 @@
 import { WebhookEvents } from "../generated/get-webhook-payload-type-from-event";
 import { webhookNames } from "../generated/webhook-names";
-import { State } from "../types";
+import { State, WebhookEvent, WebhookEventHandlerError } from "../types";
 
+function handleEventHandlers(
+  state: State,
+  webhookName: WebhookEvents,
+  handler: Function
+) {
+  if (!state.hooks[webhookName]) {
+    state.hooks[webhookName] = [];
+  }
+
+  state.hooks[webhookName].push(handler);
+}
 export function receiverOn(
   state: State,
   webhookNameOrNames: WebhookEvents | WebhookEvents[],
@@ -20,9 +31,28 @@ export function receiverOn(
     );
   }
 
-  if (!state.hooks[webhookNameOrNames]) {
-    state.hooks[webhookNameOrNames] = [];
+  if (webhookNameOrNames === "*" || webhookNameOrNames === "error") {
+    const webhookName = webhookNameOrNames === "*" ? "any" : webhookNameOrNames;
+    console.warn(
+      `Using the "${webhookNameOrNames}" event with the regular Webhooks.on() function is deprecated. Please use the Webhooks.on${
+        webhookName.charAt(0).toUpperCase() + webhookName.slice(1)
+      }() method instead`
+    );
   }
 
-  state.hooks[webhookNameOrNames].push(handler);
+  handleEventHandlers(state, webhookNameOrNames, handler);
+}
+
+export function receiverOnAny(
+  state: State,
+  handler: (event: WebhookEvent<any>) => any
+) {
+  handleEventHandlers(state, "*", handler);
+}
+
+export function receiverOnError(
+  state: State,
+  handler: (event: WebhookEventHandlerError) => any
+) {
+  handleEventHandlers(state, "error", handler);
 }
diff --git a/src/index.ts b/src/index.ts
index 44fb6d6..6ec2fb4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,6 +9,7 @@ import {
   State,
   WebhookEvent,
   WebhookError,
+  WebhookEventHandlerError,
   HandlerFunction,
 } from "./types";
 import { IncomingMessage, ServerResponse } from "http";
@@ -22,6 +23,8 @@ class Webhooks<T extends WebhookEvent = WebhookEvent, U = {}> {
     event: E | E[],
     callback: HandlerFunction<E, U>
   ) => void;
+  public onAny: (callback: (event: WebhookEvent<any>) => any) => void;
+  public onError: (callback: (event: WebhookEventHandlerError) => any) => void;
   public removeListener: <E extends WebhookEvents>(
     event: E | E[],
     callback: HandlerFunction<E, U>
@@ -55,6 +58,8 @@ class Webhooks<T extends WebhookEvent = WebhookEvent, U = {}> {
     this.sign = sign.bind(null, options.secret);
     this.verify = verify.bind(null, options.secret);
     this.on = state.eventHandler.on;
+    this.onAny = state.eventHandler.onAny;
+    this.onError = state.eventHandler.onError;
     this.removeListener = state.eventHandler.removeListener;
     this.receive = state.eventHandler.receive;
     this.middleware = middleware.bind(null, state);
diff --git a/src/middleware/middleware.ts b/src/middleware/middleware.ts
index d9985ec..fbbb093 100644
--- a/src/middleware/middleware.ts
+++ b/src/middleware/middleware.ts
@@ -42,7 +42,8 @@ export function middleware(
   }
 
   const eventName = request.headers["x-github-event"] as WebhookEvents;
-  const signature = request.headers["x-hub-signature"] as string;
+  const signatureSHA1 = request.headers["x-hub-signature"] as string;
+  const signatureSHA256 = request.headers["x-hub-signature-256"] as string;
   const id = request.headers["x-github-delivery"] as string;
 
   debugWebhooks(`${eventName} event received (id: ${id})`);
@@ -62,7 +63,7 @@ export function middleware(
         id: id,
         name: eventName,
         payload,
-        signature,
+        signature: signatureSHA256 || signatureSHA1,
       });
     })
 
diff --git a/src/sign/README.md b/src/sign/README.md
index 2c960b4..6759362 100644
--- a/src/sign/README.md
+++ b/src/sign/README.md
@@ -5,6 +5,7 @@ The `sign` method can be used as a standalone method.
 ```js
 const { sign } = require('@octokit/webhooks')
 const signature = sign(secret, eventPayload)
+const signature = sign({ secret, algorithm }, eventPayload)
 // string like "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8"
 ```
 
@@ -12,7 +13,7 @@ const signature = sign(secret, eventPayload)
   <tr>
     <td>
       <code>
-        options.secret
+        secret
       </code>
       <em>(String)</em>
     </td>
@@ -21,6 +22,19 @@ const signature = sign(secret, eventPayload)
       Secret as configured in GitHub Settings.
     </td>
   </tr>
+  <tr>
+    <td>
+      <code>
+        algorithm
+      </code>
+      <em>
+        (String)
+      </em>
+    </td>
+    <td>
+      Algorithm to calculate signature. Can be set to `sha1` or `sha256`. Defaults to `sha1`.
+    </td>
+  </tr>
   <tr>
     <td>
       <code>
diff --git a/src/sign/index.ts b/src/sign/index.ts
index d648406..cb0baff 100644
--- a/src/sign/index.ts
+++ b/src/sign/index.ts
@@ -1,14 +1,43 @@
 import { createHmac } from "crypto";
 
-export function sign(secret: string, payload: string | object): string {
+export enum Algorithm {
+  SHA1 = "sha1",
+  SHA256 = "sha256",
+}
+
+type SignOptions = {
+  secret: string;
+  algorithm?: string;
+};
+
+export function sign(
+  options: SignOptions | string,
+  payload: string | object
+): string {
+  const { secret, algorithm } =
+    typeof options === "string"
+      ? { secret: options, algorithm: Algorithm.SHA1 }
+      : {
+          secret: options.secret,
+          algorithm: options.algorithm || Algorithm.SHA1,
+        };
+
   // @ts-ignore throw friendly error message when required options are missing
   if (!secret || !payload) {
-    throw new TypeError("secret & payload required");
+    throw new TypeError("[@octokit/webhooks] secret & payload required");
+  }
+
+  if (!Object.values(Algorithm).includes(algorithm as Algorithm)) {
+    throw new TypeError(
+      `[@octokit/webhooks] Algorithm ${algorithm} is not supported. Must be  'sha1' or 'sha256'`
+    );
   }
 
   payload =
     typeof payload === "string" ? payload : toNormalizedJsonString(payload);
-  return "sha1=" + createHmac("sha1", secret).update(payload).digest("hex");
+  return `${algorithm}=${createHmac(algorithm, secret)
+    .update(payload)
+    .digest("hex")}`;
 }
 
 function toNormalizedJsonString(payload: object) {
diff --git a/src/verify/index.ts b/src/verify/index.ts
index 3c6dc8d..9bcf2ca 100644
--- a/src/verify/index.ts
+++ b/src/verify/index.ts
@@ -2,6 +2,10 @@ import { timingSafeEqual } from "crypto";
 import { Buffer } from "buffer";
 import { sign } from "../sign/index";
 
+const getAlgorithm = (signature: string) => {
+  return signature.startsWith("sha256=") ? "sha256" : "sha1";
+};
+
 export function verify(
   secret?: string,
   eventPayload?: object,
@@ -12,7 +16,10 @@ export function verify(
   }
 
   const signatureBuffer = Buffer.from(signature);
-  const verificationBuffer = Buffer.from(sign(secret, eventPayload));
+  const algorithm = getAlgorithm(signature);
+  const verificationBuffer = Buffer.from(
+    sign({ secret, algorithm }, eventPayload)
+  );
 
   if (signatureBuffer.length !== verificationBuffer.length) {
     return false;


Test patch:
diff --git a/test/integration/event-handler-test.ts b/test/integration/event-handler-test.ts
index 10c2996..43084af 100644
--- a/test/integration/event-handler-test.ts
+++ b/test/integration/event-handler-test.ts
@@ -1,7 +1,7 @@
 import { createEventHandler } from "../../src/event-handler";
 import pushEventPayload from "../fixtures/push-payload.json";
 import installationCreatedPayload from "../fixtures/installation-created-payload.json";
-import { WebhookError, WebhookEvent } from "../../src/types";
+import { WebhookEvent, WebhookEventHandlerError } from "../../src/types";
 
 test("events", (done) => {
   const eventHandler = createEventHandler({});
@@ -37,7 +37,7 @@ test("events", (done) => {
   eventHandler.on(["push"], hook4);
   eventHandler.on("installation", hook5);
   eventHandler.on("installation.created", hook6);
-  eventHandler.on("*", hook7);
+  eventHandler.onAny(hook7);
 
   eventHandler.removeListener("push", hook3);
   eventHandler.removeListener(["push"], hook4);
@@ -68,7 +68,7 @@ test("events", (done) => {
         "* (installation)",
       ]);
 
-      eventHandler.on("error", (error: WebhookError) => {
+      eventHandler.onError((error: WebhookEventHandlerError) => {
         expect(error.event.payload).toBeTruthy();
         // t.pass("error event triggered");
         expect(error.message).toMatch(/oops/);
diff --git a/test/integration/server-test.ts b/test/integration/server-test.ts
index 2a31495..98943a0 100644
--- a/test/integration/server-test.ts
+++ b/test/integration/server-test.ts
@@ -6,6 +6,10 @@ import { promisify } from "util";
 import { Webhooks } from "../../src";
 import pushEventPayload from "../fixtures/push-payload.json";
 
+const signatureSha1 = "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f";
+const signatureSha256 =
+  "sha256=2b49327af77d51c4c23700118b11d15c81d90c3bd57dafbbe32eb50085ce67e0";
+
 describe("server-test", () => {
   let availablePort: number;
 
@@ -47,6 +51,77 @@ describe("server-test", () => {
       .finally(() => server.close(t));
   });
 
+  test("POST / with push event payload with wrong sha1 but right sha256", (t) => {
+    expect.assertions(2);
+
+    const api = new Webhooks({
+      secret: "mysecret",
+    });
+    const server = http.createServer(api.middleware);
+
+    api.on("push", (event) => {
+      expect(event.id).toBe("123e4567-e89b-12d3-a456-426655440000");
+    });
+
+    promisify(server.listen.bind(server))(availablePort)
+
+      .then(() => {
+        return axios.post(
+          `http://localhost:${availablePort}`,
+          pushEventPayload,
+          {
+            headers: {
+              "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
+              "X-GitHub-Event": "push",
+              "X-Hub-Signature": "ignored",
+              "X-Hub-Signature-256": signatureSha256,
+            },
+          }
+        );
+      })
+
+      .then((result: AxiosResponse) => {
+        expect(result.status).toBe(200);
+      })
+      .catch((e) => expect(e instanceof Error).toBeTruthy())
+      .finally(() => server.close(t));
+  });
+
+  test("POST / with push event payload with only sha1", (t) => {
+    expect.assertions(2);
+
+    const api = new Webhooks({
+      secret: "mysecret",
+    });
+    const server = http.createServer(api.middleware);
+
+    api.on("push", (event) => {
+      expect(event.id).toBe("123e4567-e89b-12d3-a456-426655440000");
+    });
+
+    promisify(server.listen.bind(server))(availablePort)
+
+      .then(() => {
+        return axios.post(
+          `http://localhost:${availablePort}`,
+          pushEventPayload,
+          {
+            headers: {
+              "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
+              "X-GitHub-Event": "push",
+              "X-Hub-Signature": signatureSha1,
+            },
+          }
+        );
+      })
+
+      .then((result: AxiosResponse) => {
+        expect(result.status).toBe(200);
+      })
+      .catch((e) => expect(e instanceof Error).toBeTruthy())
+      .finally(() => server.close(t));
+  });
+
   test("POST / with push event payload", (t) => {
     expect.assertions(2);
 
@@ -69,8 +144,8 @@ describe("server-test", () => {
             headers: {
               "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
               "X-GitHub-Event": "push",
-              "X-Hub-Signature":
-                "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f",
+              "X-Hub-Signature": "sha1=foo",
+              "X-Hub-Signature-256": signatureSha256,
             },
           }
         );
@@ -120,8 +195,8 @@ describe("server-test", () => {
             headers: {
               "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
               "X-GitHub-Event": "push",
-              "X-Hub-Signature":
-                "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f",
+              "X-Hub-Signature": signatureSha1,
+              "X-Hub-Signature-256": signatureSha256,
             },
           }
         );
@@ -142,7 +217,7 @@ describe("server-test", () => {
     });
     const server = http.createServer(api.middleware);
     const errorHandler = jest.fn();
-    api.on("error", errorHandler);
+    api.onError(errorHandler);
 
     promisify(server.listen.bind(server))(availablePort)
 
@@ -177,7 +252,7 @@ describe("server-test", () => {
     });
     const server = http.createServer(api.middleware);
     const errorHandler = jest.fn();
-    api.on("error", errorHandler);
+    api.onError(errorHandler);
 
     promisify(server.listen.bind(server))(availablePort)
 
@@ -190,6 +265,7 @@ describe("server-test", () => {
               "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
               "X-GitHub-Event": "push",
               "X-Hub-Signature": "sha1=foo",
+              "X-Hub-Signature-256": "sha256=foo",
             },
           }
         );
@@ -227,8 +303,8 @@ describe("server-test", () => {
             headers: {
               "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
               "X-GitHub-Event": "push",
-              "X-Hub-Signature":
-                "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f",
+              "X-Hub-Signature": signatureSha1,
+              "X-Hub-Signature-256": signatureSha256,
             },
           }
         );
@@ -271,8 +347,8 @@ describe("server-test", () => {
             headers: {
               "X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000",
               "X-GitHub-Event": "push",
-              "X-Hub-Signature":
-                "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f",
+              "X-Hub-Signature": signatureSha1,
+              "X-Hub-Signature-256": signatureSha256,
             },
           }
         );
diff --git a/test/integration/sign-test.ts b/test/integration/sign-test.ts
index 2a7228c..1fff22a 100644
--- a/test/integration/sign-test.ts
+++ b/test/integration/sign-test.ts
@@ -20,12 +20,71 @@ test("sign(secret) without eventPayload throws", () => {
   expect(() => sign.bind(null, secret)()).toThrow();
 });
 
-test("sign(secret, eventPayload) with eventPayload as object returns expected signature", () => {
-  const signature = sign(secret, eventPayload);
-  expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+test("sign({secret, algorithm}) with invalid algorithm throws", () => {
+  // @ts-ignore
+  expect(() =>
+    sign.bind(null, { secret, algorithm: "sha2" }, eventPayload)()
+  ).toThrow();
 });
 
-test("sign(secret, eventPayload) with eventPayload as string returns expected signature", () => {
-  const signature = sign(secret, JSON.stringify(eventPayload));
-  expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+describe("with eventPayload as object", () => {
+  describe("resturns expected sha1 signature", () => {
+    test("sign(secret, eventPayload)", () => {
+      const signature = sign(secret, eventPayload);
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+
+    test("sign({secret}, eventPayload)", () => {
+      const signature = sign({ secret }, eventPayload);
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+
+    test("sign({secret, algorithm: 'sha1'}, eventPayload)", () => {
+      const signature = sign({ secret, algorithm: "sha1" }, eventPayload);
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+  });
+
+  describe("resturns expected sha256 signature", () => {
+    test("sign({secret, algorithm}, eventPayload)", () => {
+      const signature = sign({ secret, algorithm: "sha256" }, eventPayload);
+      expect(signature).toBe(
+        "sha256=4864d2759938a15468b5df9ade20bf161da9b4f737ea61794142f3484236bda3"
+      );
+    });
+  });
+});
+
+describe("with eventPayload as string", () => {
+  describe("resturns expected sha1 signature", () => {
+    test("sign(secret, eventPayload)", () => {
+      const signature = sign(secret, JSON.stringify(eventPayload));
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+
+    test("sign({secret}, eventPayload)", () => {
+      const signature = sign({ secret }, JSON.stringify(eventPayload));
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+
+    test("sign({secret, algorithm: 'sha1'}, eventPayload)", () => {
+      const signature = sign(
+        { secret, algorithm: "sha1" },
+        JSON.stringify(eventPayload)
+      );
+      expect(signature).toBe("sha1=d03207e4b030cf234e3447bac4d93add4c6643d8");
+    });
+  });
+
+  describe("returns expected sha256 signature", () => {
+    test("sign({secret, algorithm: 'sha256'}, eventPayload)", () => {
+      const signature = sign(
+        { secret, algorithm: "sha256" },
+        JSON.stringify(eventPayload)
+      );
+      expect(signature).toBe(
+        "sha256=4864d2759938a15468b5df9ade20bf161da9b4f737ea61794142f3484236bda3"
+      );
+    });
+  });
 });
diff --git a/test/integration/verify-test.ts b/test/integration/verify-test.ts
index ba6ff73..163a94e 100644
--- a/test/integration/verify-test.ts
+++ b/test/integration/verify-test.ts
@@ -4,7 +4,9 @@ const eventPayload = {
   foo: "bar",
 };
 const secret = "mysecret";
-const signature = "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8";
+const signatureSHA1 = "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8";
+const signatureSHA256 =
+  "sha256=4864d2759938a15468b5df9ade20bf161da9b4f737ea61794142f3484236bda3";
 
 test("verify() without options throws", () => {
   expect(() => verify()).toThrow();
@@ -22,22 +24,22 @@ test("verify(secret, eventPayload) without options.signature throws", () => {
   expect(() => verify.bind(null, secret, eventPayload)()).toThrow();
 });
 
-test("verify(secret, eventPayload, signature) returns true for correct signature", () => {
-  const signatureMatches = verify(secret, eventPayload, signature);
+test("verify(secret, eventPayload, signatureSHA1) returns true for correct signature", () => {
+  const signatureMatches = verify(secret, eventPayload, signatureSHA1);
   expect(signatureMatches).toBe(true);
 });
 
-test("verify(secret, eventPayload, signature) returns false for incorrect signature", () => {
+test("verify(secret, eventPayload, signatureSHA1) returns false for incorrect signature", () => {
   const signatureMatches = verify(secret, eventPayload, "foo");
   expect(signatureMatches).toBe(false);
 });
 
-test("verify(secret, eventPayload, signature) returns false for correct secret", () => {
-  const signatureMatches = verify("foo", eventPayload, signature);
+test("verify(secret, eventPayload, signatureSHA1) returns false for correct secret", () => {
+  const signatureMatches = verify("foo", eventPayload, signatureSHA1);
   expect(signatureMatches).toBe(false);
 });
 
-test("verify(secret, eventPayload, signature) returns true if eventPayload contains (#71)", () => {
+test("verify(secret, eventPayload, signatureSHA1) returns true if eventPayload contains special characters (#71)", () => {
   // https://github.com/octokit/webhooks.js/issues/71
   const signatureMatchesLowerCaseSequence = verify(
     "development",
@@ -64,3 +66,46 @@ test("verify(secret, eventPayload, signature) returns true if eventPayload conta
   );
   expect(signatureMatchesEscapedSequence).toBe(true);
 });
+
+test("verify(secret, eventPayload, signatureSHA256) returns true for correct signature", () => {
+  const signatureMatches = verify(secret, eventPayload, signatureSHA256);
+  expect(signatureMatches).toBe(true);
+});
+
+test("verify(secret, eventPayload, signatureSHA256) returns false for incorrect signature", () => {
+  const signatureMatches = verify(secret, eventPayload, "foo");
+  expect(signatureMatches).toBe(false);
+});
+
+test("verify(secret, eventPayload, signatureSHA256) returns false for correct secret", () => {
+  const signatureMatches = verify("foo", eventPayload, signatureSHA256);
+  expect(signatureMatches).toBe(false);
+});
+
+test("verify(secret, eventPayload, signatureSHA256) returns true if eventPayload contains special characters (#71)", () => {
+  // https://github.com/octokit/webhooks.js/issues/71
+  const signatureMatchesLowerCaseSequence = verify(
+    "development",
+    {
+      foo: "Foo\n\u001b[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001b[0m\u001b[2K",
+    },
+    "sha256=afecc3caa27548bb90d51a50384cb2868b9a3327b4ad6a01c9bd4ed0f8b0b12c"
+  );
+  expect(signatureMatchesLowerCaseSequence).toBe(true);
+  const signatureMatchesUpperCaseSequence = verify(
+    "development",
+    {
+      foo: "Foo\n\u001B[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001B[0m\u001B[2K",
+    },
+    "sha256=afecc3caa27548bb90d51a50384cb2868b9a3327b4ad6a01c9bd4ed0f8b0b12c"
+  );
+  expect(signatureMatchesUpperCaseSequence).toBe(true);
+  const signatureMatchesEscapedSequence = verify(
+    "development",
+    {
+      foo: "\\u001b",
+    },
+    "sha256=6f8326efbacfbd04e870cea25b5652e635be8c9807f2fd5348ef60753c9e96ed"
+  );
+  expect(signatureMatchesEscapedSequence).toBe(true);
+});
diff --git a/test/typescript-validate.ts b/test/typescript-validate.ts
index f3a9089..c829295 100644
--- a/test/typescript-validate.ts
+++ b/test/typescript-validate.ts
@@ -66,15 +66,25 @@ export default async function () {
   });
 
   sign("randomSecret", {});
+  sign({ secret: "randomSecret" }, {});
+  sign({ secret: "randomSecret", algorithm: "sha1" }, {});
+  sign({ secret: "randomSecret", algorithm: "sha256" }, {});
 
   verify("randomSecret", {}, "randomSignature");
 
+  // This is deprecated usage
   webhooks.on("*", ({ id, name, payload }) => {
     console.log(name, "event received");
     const sig = webhooks.sign(payload);
     webhooks.verify(payload, sig);
   });
 
+  webhooks.onAny(({ id, name, payload }) => {
+    console.log(name, "event received");
+    const sig = webhooks.sign(payload);
+    webhooks.verify(payload, sig);
+  });
+
   webhooks.on("check_run.completed", () => {});
 
   webhooks.on(
@@ -124,6 +134,7 @@ export default async function () {
     console.log(event.foo);
   });
 
+  // This is deprecated usage
   webhooks.on("error", (error) => {
     console.log(error.event.name);
     const [firstError] = Array.from(error);
@@ -132,6 +143,14 @@ export default async function () {
     console.log(firstError.request);
   });
 
+  webhooks.onError((error) => {
+    console.log(error.event.name);
+    const [firstError] = Array.from(error);
+    console.log(firstError.status);
+    console.log(firstError.headers);
+    console.log(firstError.request);
+  });
+
   createServer(webhooks.middleware).listen(3000);
 }
 
diff --git a/test/unit/event-handler-on-test.ts b/test/unit/event-handler-on-test.ts
index 243149f..c94738f 100644
--- a/test/unit/event-handler-on-test.ts
+++ b/test/unit/event-handler-on-test.ts
@@ -20,3 +20,21 @@ test("receiver.on with invalid event name", () => {
 
   simple.restore();
 });
+
+test("receiver.on with event name of '*' logs deprecation notice", () => {
+  simple.mock(console, "warn").callFn(function () {});
+  receiverOn(state, "*", noop);
+  expect((console.warn as simple.Stub<void>).callCount).toBe(1);
+  expect((console.warn as simple.Stub<void>).lastCall.arg).toBe(
+    'Using the "*" event with the regular Webhooks.on() function is deprecated. Please use the Webhooks.onAny() method instead'
+  );
+});
+
+test("receiver.on with event name of 'error' logs deprecation notice", () => {
+  simple.mock(console, "warn").callFn(function () {});
+  receiverOn(state, "error", noop);
+  expect((console.warn as simple.Stub<void>).callCount).toBe(1);
+  expect((console.warn as simple.Stub<void>).lastCall.arg).toBe(
+    'Using the "error" event with the regular Webhooks.on() function is deprecated. Please use the Webhooks.onError() method instead'
+  );
+});