Webhook Connectors
A webhook connector receives HTTP POST requests from an external service, verifies the payload signature, and normalizes the body into NormalizedEvent[].
Interface
Section titled “Interface”export interface IWebhookConnector extends IKairosisConnector { verifyWebhook(params: WebhookVerifyParams): Promise<boolean>; normalize(body: unknown, workspaceId: string, config: unknown): Promise<NormalizedEvent[]>;}Webhook URL format
Section titled “Webhook URL format”POST /webhooks/:connectorId/:webhookTokenThe webhookToken is an opaque random string generated when the connector is configured. It maps to a workspaceId internally. The workspace ID is never in the URL.
Implementing verifyWebhook
Section titled “Implementing verifyWebhook”Always verify the payload signature before normalizing. Most services use HMAC-SHA256:
async verifyWebhook({ body, headers, rawBody, secrets }: WebhookVerifyParams): Promise<boolean> { const sig = headers['x-hub-signature-256'] as string; const expected = 'sha256=' + createHmac('sha256', secrets.webhookSecret) .update(rawBody) .digest('hex'); return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));}rawBody is always available because the API is started with { rawBody: true }. Never use the parsed body for HMAC verification — byte-exact comparison is required.
Implementing normalize
Section titled “Implementing normalize”Map the raw payload to one or more NormalizedEvent objects:
async normalize(body: unknown, workspaceId: string, config: unknown): Promise<NormalizedEvent[]> { const payload = GithubWebhookPayloadSchema.parse(body);
return [{ id: randomUUID(), type: GithubEventType.COMMIT_PUSHED, workspaceId, occurredAt: new Date().toISOString(), source: 'github', actor: { id: payload.sender.id.toString(), name: payload.sender.login, }, subject: { id: payload.repository.full_name, type: 'repository', url: payload.repository.html_url, }, payload: GithubCommitPushedPayload.parse({ ... }), }];}Registering a webhook connector
Section titled “Registering a webhook connector”- Implement the connector in
connectors/<name>/ - Register it in
apps/api/src/connectors/connectors.module.ts:
async onModuleInit() { ConnectorRegistry.register(new GithubConnector()); ConnectorRegistry.register(new YourConnector());}- Add the path alias to
tsconfig.base.json