Skip to content

eben-vranken/trusthook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trusthook

One-call webhook signature verification for any provider, with replay protection and zero dependencies.

CI Go Reference Go Report Card Coverage MIT License

One API to verify inbound webhook signatures from any provider: Stripe, GitHub, Shopify, Slack, Discord, and more. Pure Go stdlib, zero dependencies.

Every provider signs webhooks differently (hex vs. base64, HMAC vs. Ed25519, raw body vs. timestamped strings), and getting any detail wrong silently breaks verification or, worse, leaves your endpoint forgeable. Trusthook handles the per-provider quirks, replay protection, and constant-time comparison behind a single call.

Install

go get github.com/eben-vranken/trusthook

Usage

Pick a provider, pass the raw body, the request headers, and your signing secret. Trusthook returns nil when the signature is valid, or an error when it is not.

package main

import (
	"io"
	"net/http"

	"github.com/eben-vranken/trusthook"
)

func handleWebhook(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "cannot read body", http.StatusBadRequest)
		return
	}

	err = trusthook.Verify(trusthook.GitHub, body, r.Header, "your-webhook-secret")
	if err != nil {
		http.Error(w, "invalid signature", http.StatusUnauthorized)
		return
	}

	// Signature is valid. Handle the event.
}

To support a different provider, change the provider value. Nothing else changes.

trusthook.Verify(trusthook.Stripe, body, r.Header, secret)
trusthook.Verify(trusthook.Discord, body, r.Header, secret)

Supported providers

Provider Scheme
Stripe HMAC-SHA256 (timestamped)
GitHub HMAC-SHA256 (hex)
Shopify HMAC-SHA256 (base64)
Slack HMAC-SHA256 (timestamped)
Paddle HMAC-SHA256 (timestamped)
Discord Ed25519
Standard Webhooks HMAC-SHA256
Dropbox HMAC-SHA256 (hex)
Lemon Squeezy HMAC-SHA256 (hex)
Linear HMAC-SHA256 (hex)
Coinbase Commerce HMAC-SHA256 (hex)
Razorpay HMAC-SHA256 (hex)
Zoom HMAC-SHA256 (timestamped)
Calendly HMAC-SHA256 (timestamped)
WorkOS HMAC-SHA256 (timestamped)
Svix HMAC-SHA256 (Standard Webhooks)
Clerk HMAC-SHA256 (Standard Webhooks)
Resend HMAC-SHA256 (Standard Webhooks)

Errors

Verify returns a sentinel error you can match with errors.Is:

Error Meaning
ErrInvalidProvider The provider value is not valid.
ErrInvalidSecret The secret could not be decoded.
ErrMissingHeader A required header was not present.
ErrMalformedSignature A signature header was present but could not be parsed.
ErrSignatureMismatch The signature did not match the body.
ErrTimestampOutOfRange The request timestamp was outside the allowed window.
err := trusthook.Verify(trusthook.Stripe, body, r.Header, secret)
if err != nil {
	if errors.Is(err, trusthook.ErrSignatureMismatch) {
		// reject the request
	}
}

Performance

Verification is effectively free next to the network round-trip that delivered the webhook. Representative numbers on an AMD Ryzen 5 5600X (Go 1.26):

Provider Scheme Time Allocations
GitHub HMAC-SHA256 (hex) ~670 ns 9
Stripe HMAC-SHA256 (timestamped) ~1.1 µs 16
Standard Webhooks HMAC-SHA256 (3-header, base64) ~1.3 µs 16
Discord Ed25519 ~44 µs 4

The HMAC providers verify in well under a microsecond. Discord is slower because Ed25519 is public-key verification rather than a hash, and even that is negligible compared to the network. Reproduce with:

go test -bench=. -benchmem

Testing

Run the unit tests:

go test ./...

This also runs the fuzz seed corpus. To actively fuzz Verify for 30 seconds across every provider, checking that no input can make it panic:

go test -fuzz=FuzzVerify -fuzztime=30s

If the fuzzer finds a crashing input, it saves it under testdata/fuzz/ so it becomes a permanent regression test.

License

MIT

About

🪝 One-call webhook signature verification for any provider, with replay protection and zero dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages