@kontent-ai/webhook-helper
v4.0.0
Published
This utility helps with webhook notifications from Kontent.ai
Downloads
21,998
Readme
Kontent.ai Webhook helper
This package aims to help you with Webhooks received from Kontent.ai projects. It provides:
- Runtime validation and parsing of webhook payloads with Zod
- Type-safe notification handling with TypeScript discriminated unions
- Signature verification
Installation
Install package:
npm install @kontent-ai/webhook-helper
pnpm add @kontent-ai/webhook-helperQuick Start
Create a reusable function to parse and validate webhook requests:
import { parseSignedWebhookResponse, SIGNATURE_HEADER, ParseResult, WebhookResponse } from '@kontent-ai/webhook-helper';
const parseWebhookRequest = async (
request: Request,
secret: string,
): Promise<ParseResult<WebhookResponse>> => {
const signature = request.headers.get(SIGNATURE_HEADER) ?? "";
const payload = await request.text();
return parseSignedWebhookResponse({
payload,
secret,
signature,
});
};
const handleWebhook = async (request: Request) => {
const result = await parseWebhookRequest(request, 'your-webhook-secret');
if (!result.success) {
return new Response(result.error.message, { status: 401 });
}
result.data.notifications.forEach(notification => {
if (notification.object_type === 'content_item') {
console.log('Content item:', notification.data.system.name);
}
});
return new Response('OK', { status: 200 });
};Parsing & Validating Webhooks
The library provides two main parsing functions:
Parse with signature verification
import { parseSignedWebhookResponse } from '@kontent-ai/webhook-helper';
const result = parseSignedWebhookResponse({payload, secret, signature});
if (!result.success) {
console.error('Validation failed:', result.error);
return;
}
const webhookData = result.data;Parse without signature verification
import { parseWebhookResponse } from '@kontent-ai/webhook-helper';
const result = parseWebhookResponse(body);
if (!result.success) {
console.error('Parsing failed:', result.error);
return;
}
const webhookData = result.data;Type-Safe Notification Handling
Each notification has an object_type discriminator that enables type narrowing:
const result = parseWebhookResponse(body);
if (result.success) {
result.data.notifications.forEach(notification => {
switch (notification.object_type) {
case 'content_item':
console.log('Workflow step:', notification.data.system.workflow_step);
console.log('Action:', notification.message.action);
break;
case 'asset':
console.log('Asset ID:', notification.data.system.id);
console.log('Action:', notification.message.action);
break;
case 'content_type':
console.log('Type codename:', notification.data.system.codename);
break;
case 'language':
console.log('Language codename:', notification.data.system.codename);
break;
case 'taxonomy':
console.log('Taxonomy codename:', notification.data.system.codename);
break;
case 'unknown':
console.warn('Unknown notification type:', notification.original_notification);
break;
}
});
}Notification Structure & object_type
To enable the type-safe discrimination shown above, this library "uplifts" the object_type property to the top level of the notification object. This is a convenience helper that creates a discriminated union, allowing TypeScript to automatically narrow the types.
This means the parsed notification object is a slight superset of the original Kontent.ai webhook payload.
If you need to strictly match the original notification shape (without the top-level object_type), you can use the Omit utility type:
import { AssetNotification } from '@kontent-ai/webhook-helper';
// Removes the helper 'object_type' property to match the raw payload structure
type RawAssetNotification = Omit<AssetNotification, "object_type">;Handling Unknown Webhooks
The library is forward-compatible with future webhook types. When a notification doesn't match any known schema, it's typed as UnknownNotification:
type UnknownNotification = {
object_type: "unknown";
original_notification: Record<PropertyKey, unknown>;
};This allows you to:
- Access raw webhook data via
original_notification - Handle future webhook types without breaking your application
- Use exhaustive type checking in switch statements
result.data.notifications.forEach(notification => {
if (notification.object_type === 'unknown') {
console.log('Unrecognized webhook:', notification.original_notification);
}
});Signature Verification
If you need to verify signatures separately from parsing, use isSignatureValid():
import { isSignatureValid, replaceLinebreaks, SIGNATURE_HEADER } from '@kontent-ai/webhook-helper';
const verifyWebhookSignature = async (request: Request, secret: string): Promise<boolean> => {
const signature = request.headers.get(SIGNATURE_HEADER);
const payload = await request.text();
return isSignatureValid({payload, secret, signature});
}
if (!await verifyWebhookSignature(request, 'your-webhook-secret')) {
throw new Error('Invalid signature');
}The payload must be exactly the same (including whitespaces) as the original webhook body. If you've parsed the payload into an object, you can reconstruct it:
const payload = JSON.stringify(jsonPayload, null, 2);The replaceLinebreaks() function normalizes line endings to prevent signature mismatches caused by Windows line breaks:
import { replaceLinebreaks } from '@kontent-ai/webhook-helper';
const normalizedPayload = replaceLinebreaks(payload);