Design and implement n8n workflows and webhook handlers for the secretary bot. Use when: designing an n8n workflow for audio ingest, transcription, extraction, or Telegram delivery; writing a webhook handler that n8n will call; defining typed payload schemas for n8n-to-service communication; configuring retries and error branches; enforcing idempotency via sessionId or correlationId; or validating environment config at startup.
Guide the design of n8n workflows that orchestrate the secretary bot pipeline. Use this skill when designing, reviewing, or documenting n8n workflow JSON or when writing TypeScript code that acts as an n8n HTTP endpoint.
n8n is a workflow orchestrator, not a business logic layer. All business rules live in the TypeScript service. n8n only:
Every n8n → service call uses an explicit typed payload. Define these in src/workflows/types.ts. Never rely on implicit n8n node output shapes.
// src/workflows/types.ts
export interface AudioIngestPayload {
readonly sessionId: string; // client-generated stable UUID
readonly telegramFileId?: string; // set when source is Telegram voice message
readonly uploadUrl?: string; // set when source is direct upload
readonly durationSeconds: number;
readonly mimeType: string;
}
export interface TranscriptionRequestPayload {
readonly sessionId: string;
readonly filePath: string;
readonly mimeType: string;
}
export interface ExtractionRequestPayload {
readonly sessionId: string;
readonly transcript: string;
readonly transcribedAt: string; // ISO 8601
}
export interface DeliveryRequestPayload {
readonly correlationId: string;
readonly chatId: string;
readonly kind: string;
readonly text: string;
readonly replyMarkup?: unknown; // validated inside delivery adapter
}
Validate all incoming n8n payloads with Zod (or equivalent) at the service boundary. Reject and log invalid payloads immediately.
kebab-case, prefixed by domain. E.g. audio-ingest, daily-briefing, decision-prompt.Receive voice upload, Call transcription, Send Telegram message.sessionId or correlationId) and send a Telegram error notice to the user.sessionId or correlationId through every node.Webhook handlers in the TypeScript service follow this pattern:
// src/workflows/handlers/audioIngestHandler.ts
import { z } from 'zod';
import type { Request, Response } from 'express';
const AudioIngestPayloadSchema = z.object({
sessionId: z.string().uuid(),
telegramFileId: z.string().optional(),
uploadUrl: z.string().url().optional(),
durationSeconds: z.number().positive().max(600),
mimeType: z.string(),
});
export async function audioIngestHandler(req: Request, res: Response): Promise<void> {
const parsed = AudioIngestPayloadSchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: 'Invalid payload', details: parsed.error.flatten() });
return;
}
// hand off to domain service — no business logic here
}
sessionId / correlationId on entry and on success / failure.All secrets and configuration values come from environment variables. Validate them at startup, not lazily.
// src/config.ts
import { z } from 'zod';
const EnvSchema = z.object({
TELEGRAM_BOT_TOKEN: z.string().min(1),
TELEGRAM_CHAT_ID: z.string().min(1),
N8N_WEBHOOK_SECRET: z.string().min(32),
TRANSCRIPTION_BACKEND: z.enum(['local', 'openai']),
MAX_AUDIO_DURATION_SECONDS: z.coerce.number().positive().default(600),
});
export const config = EnvSchema.parse(process.env);
Never read process.env outside of src/config.ts.
sessionId / correlationId is present on every structured log entry in handlers.