@zanii/webhooks
v0.1.0
Published
Zanii webhook receiver: verify the X-Zanii-Signature (HMAC-SHA256) and dispatch typed events. The safe, boilerplate-free way to react to agent activity.
Readme
@zanii/webhooks
Verify and dispatch Zanii webhook deliveries — the safe, boilerplate-free way to
react to your agents' activity. Zanii POSTs a signed JSON event for each
receipt.recorded (an agent acted) and receipt.rejected (out-of-scope, revoked,
or invalid). This package verifies the X-Zanii-Signature header (HMAC-SHA256 of
the raw body, timing-safe) and routes the typed event to your handlers, so an
unverified delivery never reaches your code.
Zero dependencies (Node's built-in crypto). Node 18+.
npm install @zanii/webhooksUse it
Give the receiver the raw request body (exactly the bytes Zanii signed — don't re-serialize) and the signature header.
Express:
import express from 'express';
import { createWebhookReceiver } from '@zanii/webhooks';
const receive = createWebhookReceiver({
secret: process.env.ZANII_WEBHOOK_SECRET!, // the whsec_… shown once at registration
on: {
'receipt.recorded': (e) => save(e.data),
'receipt.rejected': (e) => alertTeam(e.data),
},
});
const app = express();
app.post('/zanii', express.raw({ type: 'application/json' }), async (req, res) => {
const { status } = await receive(req.body, req.header('x-zanii-signature'));
res.status(status).end(); // 401 bad sig · 400 bad body · 500 handler error · 200 ok
});Next.js (App Router):
import { createWebhookReceiver } from '@zanii/webhooks';
const receive = createWebhookReceiver({ secret: process.env.ZANII_WEBHOOK_SECRET!, on: { /* … */ } });
export async function POST(req: Request) {
const raw = await req.text(); // raw body — required for the signature
const { status } = await receive(raw, req.headers.get('x-zanii-signature'));
return new Response(null, { status });
}API
createWebhookReceiver({ secret, on?, onAny? })→(rawBody, signature) => Promise<{ ok, status, event, error }>verifySignature(rawBody, signature, secret)→boolean(constant-time)sign(rawBody, secret)→'sha256=<hex>'(for tests / symmetry)- Events:
receipt.recorded,receipt.rejected.ZaniiEvent = { event, ts, data:{ hash, index, agent_id, action, target, ts, error } }.
The receiver never dispatches on a bad signature. Always send back the returned
status. Get your whsec_… secret from POST /v1/account/webhooks (see the Zanii docs).
Changelog
- 0.1.0 — initial release:
verifySignature,sign,createWebhookReceiver.
License
Apache-2.0.
