Skip to content

Express

The signature covers the raw body, so the webhook route must read it as bytes, before any JSON parser runs. express.raw does that for one route.

Terminal window
npm install express ricochet-webhooks
server.js
import express from 'express';
import { verify, WebhookVerificationError } from 'ricochet-webhooks';
const app = express();
const secret = process.env.RICOCHET_WEBHOOK_SECRET;
const handled = new Set(); // use your database in production
app.post('/webhooks', express.raw({ type: '*/*' }), (req, res) => {
let id;
try {
({ id } = verify(req.body, req.headers, secret));
} catch (error) {
if (error instanceof WebhookVerificationError) return res.status(401).send(error.reason);
throw error;
}
if (handled.has(id)) return res.sendStatus(200);
const event = JSON.parse(req.body);
console.log('received', event.type ?? req.get('x-github-event'));
handled.add(id);
res.sendStatus(200);
});
app.listen(3000);

req.body is a Buffer and req.headers is passed as is. Answer 2xx once the event is handled; any 5xx makes Ricochet retry it.

If the app registers express.json() globally, register the webhook route before it, or the body arrives parsed and the signature no longer matches.

Terminal window
node server.js
ricochet listen --forward localhost:3000/webhooks

Deliveries through the CLI carry no webhook-signature (a CLI destination has no signing secret), so turn the check off in local development, for example with an environment variable. Never skip it just because the header is missing: that would accept unsigned requests in production too.