Skip to the content
Krizaka TS
Krizaka TS

@krizaka/webhooks

Signed, idempotent webhooks, both ways: delivered with retries, verified and applied once.

Install

bashTerminal
npm install @krizaka/webhooks

Signed and idempotent webhooks, both ways.

Out. An owner registers endpoints (https, public host; the secret is shown once and kept sealed); the product emits an event with a stable id, which is queued once per endpoint however often it is emitted; deliverDue sends what is due, signed (x-krizaka-signature: t=<s>,v1=<hmac of "<t>.<body>">), retries with a backoff, gives up after maxAttempts and keeps the log (deliveries).

In. receiveWebhook verifies the signature over the raw body (with a replay window), records the event id first, applies once; a replay is "duplicate"; if applying throws, the record is removed so the sender's retry applies it.

import { createWebhooks, webhooksMigration, receiveWebhook } from "@krizaka/webhooks";

await db.query(webhooksMigration("accounts")); // once, in your migrations (idempotent)
const hooks = createWebhooks({ query, rules: { events: ["booking.created", "invoice.paid"], maxAttempts: 6, backoffSeconds: [30, 300, 1800, 7200, 43200] }, secrets: { seal, open } });
await hooks.emit(ownerId, { id: `booking:${id}`, type: "booking.created", payload }, now);
await hooks.deliverDue(now, send); // from a tick: `send` is your fetch (no redirects, short timeout, refuses private addresses)

The product keeps: the HTTP call (send), sealing the secret, when deliverDue runs, the quota per plan, its audit and its routes. Nothing here reads the clock.

Exports

The 22 names the package exports, read from its types.

NameKindWhat it is
createWebhooksfunctionOutgoing webhooks over the product's PostgreSQL (see webhooksMigration): endpoints, one queued delivery per endpoint and event, signed retries, a log.
DeliveryViewtype
endpointUrlProblemfunction
EndpointViewtype
EVENT_ID_HEADERconstant
EVENT_TYPE_HEADERconstant
newEndpointSecretfunctionA new endpoint secret: shown once, kept sealed.
OutgoingRequesttype
QuerytypeA SQL executor: the rows of a statement and its parameters (the product's pool).
receiveWebhookfunctionAn incoming webhook, applied exactly once: the signature is checked over the RAW body (a bad one is WEBHOOK_SIGNATURE), the event id is recorded first (a r…
SendtypeThe product's HTTP call: no redirects, a short timeout, and it refuses to connect to a private address.
SIGNATURE_HEADERconstantThe signature of a webhook: t=&lt;unix seconds>,v1=&lt;hex HMAC-SHA256 of "&lt;t>.&lt;raw body>"> (the scheme payment processors use, so a receiver's code is the one e…
signBodyfunctionSigning is @krizaka/payments' own (the same scheme, one implementation): this adds the header name and a verdict instead of a throw.
VerifyResulttype
verifySignaturefunctionChecks a signature header over the raw body: well-formed, not older than the tolerance (or from the future), and equal in constant time.
WEBHOOK_CODEconstant
WebhookCodetype
WebhookDepstype
WebhookErrorclass
WebhookRulestype
Webhookstype
webhooksMigrationfunctionThe tables of the webhooks: an owner's endpoints, the deliveries queued for them (one per endpoint and event — queuing twice is a no-op), and the inbound eve…

Package

Version on npm0.2.0 (npm)
LicenceApache-2.0
Node.js>=22
Dependenciesdate-fns ^4.4.0, @krizaka/payments ^0.1.0
Sourcewebhooks

Changelog

0.2.0

Minor changes

  • Signing and verification are @krizaka/payments' own (one implementation of the scheme, no copy).

0.1.0

Minor changes

  • First release: signatures and their verification, the endpoint address rules, outgoing endpoints / queue / delivery with retries and a log, incoming events applied once, the PostgreSQL migration.

On this page