zap-webhooks-routing-and-verification
SKILL.md
@zap-studio/webhooks — Routing and Verification
Setup
import { createWebhookRouter } from '@zap-studio/webhooks';
import { createHmacVerifier } from '@zap-studio/webhooks/verify';
import { z } from 'zod';
const router = createWebhookRouter({
prefix: '/webhooks/',
verify: createHmacVerifier({
headerName: 'x-hub-signature-256',
secret: process.env.WEBHOOK_SECRET!,
}),
});
router.register('github/push', {
schema: z.object({ ref: z.string() }),
handler: async ({ payload, ack }) => {
console.log(payload.ref);
return ack({ status: 200, body: 'ok' });
},
});
Core Patterns
Add global hooks for observability and error shaping
const router = createWebhookRouter({
before: (req) => {
console.log('incoming', req.path);
},
after: (_req, res) => {
console.log('status', res.status);
},
onError: (error) => ({
status: 500,
body: { error: error.message },
}),
});
Register route-specific hooks
router.register('payments/succeeded', {
schema: PaymentSchema,
before: (req) => {
req.headers.set('x-processed', '1');
},
after: (_req, res) => {
console.log('finished', res.status);
},
handler: async ({ payload, ack }) => ack({ body: { id: payload.id } }),
});
Implement an adapter with BaseAdapter
import { BaseAdapter } from '@zap-studio/webhooks/adapters/base';
class MyAdapter extends BaseAdapter {
async toNormalizedRequest(req: Request) {
return {
method: req.method,
path: req.url,
headers: req.headers,
rawBody: Buffer.from(await req.text()),
};
}
async toFrameworkResponse(res: Response, normalized) {
return new Response(JSON.stringify(normalized.body), {
status: normalized.status,
headers: normalized.headers,
}) as unknown as Response;
}
}
Common Mistakes
HIGH Registering paths with leading slash
Wrong:
router.register('/github/push', {
schema: PushSchema,
handler,
});
Correct:
router.register('github/push', {
schema: PushSchema,
handler,
});
Incoming paths normalize to slashless keys; leading slash route keys do not match and return 404.
Source: zap-studio/monorepo:packages/webhooks/src/index.ts
CRITICAL Verifying a transformed payload instead of raw body
Wrong:
const parsed = JSON.parse(req.rawBody.toString());
await verifyProvider(JSON.stringify(parsed));
Correct:
await verify(req); // uses req.rawBody bytes
const parsed = JSON.parse(req.rawBody.toString());
Signature checks must run on exact raw bytes; any parse/serialize transformation can invalidate signatures.
Source: zap-studio/monorepo:packages/webhooks/src/verify.ts
HIGH Using Node HMAC verifier in non-Node runtime
Wrong:
const verify = createHmacVerifier({
headerName: 'x-signature',
secret: env.WEBHOOK_SECRET,
});
// used in edge runtime
Correct:
const verify = async (req) => {
// implement provider verification with Web Crypto in edge runtimes
};
createHmacVerifier imports node:crypto and is intended for Node-compatible runtimes.
Source: zap-studio/monorepo:packages/webhooks/src/verify.ts
See also: zap-validation-standard-schema/SKILL.md — payload validation result handling.
Weekly Installs
1
Repository
zap-studio/monorepoGitHub Stars
153
First Seen
9 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1