Modern webhook signatures in 2021
This post is a follow-up to the previous post on x-hub-signature
It's 2021, and you work for Cat Bonnets Online. The bigwigs up on the top floor just finished telling you over Zoom that the company is modernizing its strategy. While half the company is converting the bonnets to NFTs (Nice Feline Toppings), and the rest is working on a Clubhouse strategy, your team is adding webhooks.
You want to sign your webhooks, so receivers can verify you sent them. Don't use
x-hub-signature
for this. It's not good enough, and there's a better option in 2021:
The "Signing HTTP Messages" draft specification. This new specification
addresses three shortcomings in x-hub-signature
:
Headers can be included in the signature: If your webhook includes important information in headers (for example, event or account identifiers), it should be signed, so the receiver can ensure they haven't been tampered with.
There's a facility for expiration: Sent requests can have an expiration time, mitigating replay attacks. If your events are not idempotent (or the receiver doesn't treat them as such) a bad actor could intercept a request, and repeatedly send it to the reciever. This could lead to resource exhaustion, lost data, or inconsistent state.
Asymmetric keys are supported: x-hub-signature
implementations use symmetric
keys for signing and verification. While this is faster than asymmetric forms, it
means a bad actor could steal the key from your system, from the receiver, or while
it's in transit between the two during setup. With asymmetric keys, you only need
worry about having the key stolen from your own system.
The first two issues with x-hub-signature
(unsigned headers and no expiration) can
be solved in a layer above the signature, by including all details only in the
message body, and having an application defined expiration. It's too easy to let
important data slip into the headers over time, or for a receiver to not implement
the extra step of checking the expiration time. Why not let a specification (and
hopefully a standard library) handle it for you and your receivers?
At Manifold, we implemented request signing based on an earlier version of the spec, but modified to address some shortcomings in the spec at the time (notably that while header contents were included in the signature, the header names were not). You can read about that implementation, and see code samples, on Manifold's request signing documentation. We deviated from the spec at the time, but now, you don't have to.