ctx.verify* does that check in one line: it reads the signature header, resolves the secret from your secrets, and throws if it does not match.
Stripe
index.ts
The
event your handler receives is already the Zod-validated body. The platform consumed the request body to validate it, so calling request.json() inside the handler throws — use the first parameter.Supported providers
Stripe additionally rejects events older than 5 minutes, which stops someone from replaying a captured event.
Other services
For Twilio, GitHub, Slack or any service with an HMAC-SHA256 signature, usectx.verifyHmac. The header is required because there is no default:
Rotate secrets
During a rotation, accept both the old and the new secret at once by comma-separating them:Handle the failure
Verifiers throwWebhookVerificationError with a code telling you what happened:
Public functions
Webhooks needconfig.public: true so the external service can call them without Jelou credentials. That is exactly why signature verification is mandatory: it is the only access control left.
Testing
In tests,createMockContext() leaves every verifier throwing missing_secret. To exercise the rest of the handler, bypass verification:
Public functions
Receive requests without Jelou credentials.
Secrets
Store each provider’s secret.
Deferred runs
Book a follow-up when the webhook arrives.
Webhook receiver
Full copy-paste example.