Skip to content

@k-msg/webhook

Runtime-first webhook package for message events.

This package now follows a DX-first flow:

  1. Start in 5 minutes with in-memory persistence
  2. Move to production by swapping persistence to D1
  3. Extend to SQLite/Drizzle(Postgres) with the same store contract
Terminal window
npm install @k-msg/webhook
# or
bun add @k-msg/webhook

@k-msg/webhook root exports runtime-only APIs:

  • WebhookRuntimeService
  • createInMemoryWebhookPersistence
  • addEndpoints, probeEndpoint
  • validateEndpointUrl

Advanced building blocks are now exposed from subpaths:

  • @k-msg/webhook/toolkit
  • @k-msg/webhook/adapters/cloudflare
import {
WebhookEventType,
WebhookRuntimeService,
createInMemoryWebhookPersistence,
type WebhookConfig,
} from "@k-msg/webhook";
const config: WebhookConfig = {
maxRetries: 3,
retryDelayMs: 1_000,
timeoutMs: 30_000,
enableSecurity: false,
enabledEvents: [
WebhookEventType.MESSAGE_SENT,
WebhookEventType.MESSAGE_FAILED,
WebhookEventType.SYSTEM_MAINTENANCE,
],
batchSize: 10,
batchTimeoutMs: 5_000,
};
const runtime = new WebhookRuntimeService({
delivery: config,
persistence: createInMemoryWebhookPersistence(),
});
await runtime.addEndpoint({
url: "https://example.com/webhooks/k-msg",
active: true,
events: [WebhookEventType.MESSAGE_SENT, WebhookEventType.MESSAGE_FAILED],
});
await runtime.emitSync({
id: crypto.randomUUID(),
type: WebhookEventType.MESSAGE_SENT,
timestamp: new Date(),
data: { messageId: "msg_123", status: "sent" },
metadata: { providerId: "iwinv", messageId: "msg_123" },
version: "1.0",
});
await runtime.shutdown();
import {
WebhookEventType,
WebhookRuntimeService,
type WebhookConfig,
} from "@k-msg/webhook";
import { createD1WebhookPersistence } from "@k-msg/webhook/adapters/cloudflare";
type Env = {
DB: D1Database;
};
const config: WebhookConfig = {
maxRetries: 3,
retryDelayMs: 1_000,
timeoutMs: 30_000,
enableSecurity: false,
enabledEvents: [WebhookEventType.MESSAGE_SENT, WebhookEventType.MESSAGE_FAILED],
batchSize: 10,
batchTimeoutMs: 5_000,
};
function createRuntime(env: Env): WebhookRuntimeService {
return new WebhookRuntimeService({
delivery: config,
persistence: createD1WebhookPersistence(env.DB),
security: {
allowPrivateHosts: true,
},
});
}

createD1WebhookPersistence() initializes schema automatically by default.

import {
buildWebhookSchemaSql,
initializeWebhookSchema,
} from "@k-msg/webhook/adapters/cloudflare";
const statements = buildWebhookSchemaSql();
// run statements in your migration system, or:
await initializeWebhookSchema(env.DB);

WebhookRuntimeService accepts custom stores via endpointStore + deliveryStore. Implement the same interfaces to plug any backend:

import type {
WebhookDeliveryStore,
WebhookEndpointStore,
} from "@k-msg/webhook";
class SqliteEndpointStore implements WebhookEndpointStore {
async add() {}
async update() {}
async remove() {}
async get() {
return null;
}
async list() {
return [];
}
}
class SqliteDeliveryStore implements WebhookDeliveryStore {
async add() {}
async list() {
return [];
}
}

Then wire it without changing runtime logic:

const runtime = new WebhookRuntimeService({
delivery: config,
endpointStore: new SqliteEndpointStore(),
deliveryStore: new SqliteDeliveryStore(),
});
  • Private hosts are blocked by default
  • http://localhost style URLs require explicit allowance (runtime security options)
Old usageNew usage
WebhookService (root)WebhookRuntimeService (root)
registerEndpoint() auto test calladdEndpoint() only; test with probeEndpoint()
Advanced classes from rootimport from @k-msg/webhook/toolkit
Cloudflare persistence from custom wiringuse @k-msg/webhook/adapters/cloudflare
import { LoadBalancer, QueueManager } from "@k-msg/webhook/toolkit";

MIT