The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Torah Router",
"active": true,
"nodes": [
{
"parameters": {
"content": "## Torah Router v2.0\n\n**Endpoint:** `POST /webhook/torah-router`\n\n**BREAKING CHANGES v2.0:**\n- `kind` field REQUIRED ('segment' | 'commentary')\n- No silent fallback to pending_translations\n- Strict UUID validation\n\n**Validation:**\n- kind='segment' \u2192 segment_id UUID required\n- kind='commentary' \u2192 commentary_detail_id UUID required\n- Missing/invalid \u2192 HTTP 400 (no fallback)\n\n**Pipelines:**\n- Batch: segments[] \u2192 Translate each \u2192 Save\n- Single: text \u2192 Translate \u2192 Save\n- Long text: Chunk \u2192 Translate \u2192 Save",
"height": 380,
"width": 340,
"color": 5
},
"id": "sticky-doc",
"name": "Documentation",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
48,
48
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "torah-router",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
448,
304
]
},
{
"parameters": {
"jsCode": "// Torah Router v2.0 - Strict Validation\n// NO silent fallback - reject invalid payloads with HTTP 400\n\nconst input = $input.first().json;\nconst body = input.body || input;\nconst headers = input.headers || {};\n\nconst CHUNK_THRESHOLD = 10000;\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nconst VALID_KINDS = ['segment', 'commentary'];\n\n// Helper: validate UUID\nconst isValidUUID = (str) => str && UUID_REGEX.test(str);\n\n// Helper: create error response\nconst errorResponse = (code, message) => [{\n json: {\n valid: false,\n error: { code: 400, errorCode: code, message: message }\n }\n}];\n\n// ============================================\n// VALIDATION v2.0: Strict, no fallback\n// ============================================\n\n// 1. Validate 'kind' field (REQUIRED)\nconst kind = body.kind;\nif (!kind) {\n return errorResponse('MISSING_KIND', \"Field 'kind' is required. Must be 'segment' or 'commentary'.\");\n}\nif (!VALID_KINDS.includes(kind)) {\n return errorResponse('INVALID_KIND', `Invalid kind '${kind}'. Must be 'segment' or 'commentary'.`);\n}\n\n// 2. Validate conditional IDs based on kind\nif (kind === 'segment') {\n const segmentId = body.segment_id;\n if (!segmentId) {\n return errorResponse('MISSING_SEGMENT_ID', \"Field 'segment_id' is required when kind='segment'.\");\n }\n if (!isValidUUID(segmentId)) {\n return errorResponse('INVALID_UUID', `Field 'segment_id' must be a valid UUID. Got: '${segmentId}'.`);\n }\n}\n\nif (kind === 'commentary') {\n const commentaryDetailId = body.commentary_detail_id;\n if (!commentaryDetailId) {\n return errorResponse('MISSING_COMMENTARY_DETAIL_ID', \"Field 'commentary_detail_id' is required when kind='commentary'.\");\n }\n if (!isValidUUID(commentaryDetailId)) {\n return errorResponse('INVALID_UUID', `Field 'commentary_detail_id' must be a valid UUID. Got: '${commentaryDetailId}'.`);\n }\n}\n\n// 3. Validate api_key\nif (!body.api_key) {\n return errorResponse('MISSING_API_KEY', \"Field 'api_key' is required in the request body.\");\n}\n\n// 4. Validate text or segments\nif (!body.text && (!body.segments || !Array.isArray(body.segments) || body.segments.length === 0)) {\n return errorResponse('MISSING_TEXT', \"Either 'text' or 'segments[]' is required.\");\n}\n\n// ============================================\n// PARSING: Build internal structure\n// ============================================\n\nconst jobIdProvided = !!body.job_id;\nconst jobId = body.job_id || 'job_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 8);\nconst projectId = headers['x-project-id'] || body.project_id || 'default';\nconst requestId = body.request_id || 'req_' + Date.now().toString(36);\n\nlet pipelineType = 'unknown';\nlet segments = [];\nlet needsChunking = false;\nlet totalSegments = 0;\n\nif (body.segments && Array.isArray(body.segments) && body.segments.length > 0) {\n // Batch mode: multiple segments\n pipelineType = 'batch';\n \n // Validate each segment has required ID based on kind\n for (let idx = 0; idx < body.segments.length; idx++) {\n const s = body.segments[idx];\n \n if (kind === 'segment' && !isValidUUID(s.segment_id)) {\n return errorResponse('INVALID_SEGMENT_IN_BATCH', `Segment[${idx}]: 'segment_id' must be a valid UUID when kind='segment'.`);\n }\n if (kind === 'commentary' && !isValidUUID(s.commentary_detail_id)) {\n return errorResponse('INVALID_SEGMENT_IN_BATCH', `Segment[${idx}]: 'commentary_detail_id' must be a valid UUID when kind='commentary'.`);\n }\n }\n \n segments = body.segments.map((s, idx) => {\n const text = typeof s === 'string' ? s : (s.text || s.text_hebrew || s.hebrew_text || '');\n return {\n index: idx,\n text: text,\n segment_id: kind === 'segment' ? s.segment_id : null,\n commentary_detail_id: kind === 'commentary' ? s.commentary_detail_id : null,\n reference_translation: s.reference_translation || null,\n char_count: text.length\n };\n });\n \n // Check max length\n const tooLongSegment = segments.find(s => s.char_count > CHUNK_THRESHOLD);\n if (tooLongSegment) {\n return errorResponse('SEGMENT_TOO_LONG', `Segment ${tooLongSegment.index} is too long (${tooLongSegment.char_count} chars). Max: ${CHUNK_THRESHOLD}.`);\n }\n \n totalSegments = segments.length;\n \n} else if (body.text) {\n // Single text mode\n const textLength = body.text.length;\n \n if (textLength > CHUNK_THRESHOLD) {\n pipelineType = 'chunk_then_translate';\n needsChunking = true;\n totalSegments = 1; // Will be updated after chunking\n } else {\n pipelineType = 'translate_single';\n totalSegments = 1;\n }\n \n segments = [{\n index: 0,\n text: body.text,\n segment_id: kind === 'segment' ? body.segment_id : null,\n commentary_detail_id: kind === 'commentary' ? body.commentary_detail_id : null,\n reference_translation: body.reference_translation || null,\n char_count: textLength\n }];\n}\n\n// ============================================\n// OUTPUT: Validated and parsed data\n// ============================================\n\nreturn [{\n json: {\n valid: true,\n // Routing\n kind: kind,\n jobId: jobId,\n jobIdProvided: jobIdProvided,\n needsJobCreation: !jobIdProvided,\n projectId: projectId,\n requestId: requestId,\n // Pipeline\n jobType: body.job_type || 'translation',\n pipelineType: pipelineType,\n needsChunking: needsChunking,\n // Segments\n segments: segments,\n totalSegments: totalSegments,\n // Context\n traite: body.traite || body.context?.traite,\n page: body.page || body.context?.page,\n section: body.section || body.context?.section,\n commentator: body.commentator || body.context?.commentator,\n // Languages\n sourceLanguage: body.source_language || 'he',\n targetLanguage: body.target_language || 'fr',\n // Auth\n apiKey: body.api_key,\n openaiApiKey: body.openai_api_key,\n // Metadata\n context: body.context || {},\n metadata: body.metadata || {},\n chunkThreshold: CHUNK_THRESHOLD\n }\n}];"
},
"id": "parse-input",
"name": "Parse Input",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
304
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json.valid }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-valid",
"name": "Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
880,
304
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.N8N_WEBHOOK_URL || 'http://localhost:5678' }}/webhook/torah-error",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ job_id: 'validation_rejection_' + Date.now(), segment_index: 0, worker: 'torah-router-validation', error: { code: $json.error?.errorCode || 'VALIDATION_ERROR', message: $json.error?.message || 'Unknown validation error' }, context: { validation_failed: true, error_code: $json.error?.errorCode, rejected_at: new Date().toISOString() } }) }}",
"options": {
"timeout": 5000
}
},
"id": "log-rejected-payload",
"name": "Log Rejected Payload",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1104,
512
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: false, error: { code: $('Parse Input').first().json.error.errorCode, message: $('Parse Input').first().json.error.message } }) }}",
"options": {
"responseCode": "={{ $('Parse Input').first().json.error?.code || 400 }}"
}
},
"id": "respond-error",
"name": "Respond Error",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1328,
512
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, job_id: $json.jobId, kind: $json.kind, pipeline: $json.pipelineType, segments_count: $json.totalSegments }) }}",
"options": {
"responseCode": 202
}
},
"id": "respond-accepted",
"name": "Respond Accepted",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1104,
208
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $('Parse Input').first().json.needsJobCreation }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "needs-job-creation",
"name": "Needs Job Creation?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1328,
208
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.API_URL }}/api/v2/jobs",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Project-ID",
"value": "={{ $('Parse Input').first().json.projectId }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ job_id: $('Parse Input').first().json.jobId, job_type: $('Parse Input').first().json.jobType, kind: $('Parse Input').first().json.kind, status: 'pending', input: { traite: $('Parse Input').first().json.traite, page: $('Parse Input').first().json.page, target_language: $('Parse Input').first().json.targetLanguage, segments_count: $('Parse Input').first().json.totalSegments, commentator: $('Parse Input').first().json.commentator } }) }}",
"options": {
"timeout": 10000
}
},
"id": "create-job",
"name": "Create Job",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1552,
112
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $env.API_URL }}/api/v2/jobs/{{ $('Parse Input').first().json.jobId }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ status: 'processing', progress: { current: 0, total: $('Parse Input').first().json.totalSegments, percentage: 0 } }) }}",
"options": {
"timeout": 5000
}
},
"id": "set-processing",
"name": "Set Processing",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1760,
320
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $('Parse Input').first().json.needsChunking }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "needs-chunking",
"name": "Needs Chunking?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1552,
272
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.N8N_WEBHOOK_URL || 'http://localhost:5678' }}/webhook/torah-chunk",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ job_id: $('Parse Input').first().json.jobId, text: $('Parse Input').first().json.segments[0].text, segment_id: $('Parse Input').first().json.segments[0].segment_id, commentary_detail_id: $('Parse Input').first().json.segments[0].commentary_detail_id, kind: $('Parse Input').first().json.kind, threshold: $('Parse Input').first().json.chunkThreshold, api_key: $('Parse Input').first().json.apiKey, context: { traite: $('Parse Input').first().json.traite, page: $('Parse Input').first().json.page, section: $('Parse Input').first().json.section } }) }}",
"options": {
"timeout": 300000
}
},
"id": "call-chunk-worker",
"name": "Call Chunk Worker",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1760,
112
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// Prepare segments for translation loop (after chunking)\nconst input = $('Parse Input').first().json;\nconst chunkResult = $input.first().json;\n\nlet segments = [];\n\nif (chunkResult.segments && Array.isArray(chunkResult.segments)) {\n segments = chunkResult.segments.map((s, idx) => ({\n index: idx,\n text: s.text,\n segment_id: input.segments[0]?.segment_id,\n commentary_detail_id: input.segments[0]?.commentary_detail_id,\n char_count: s.text?.length || 0,\n isChunk: true,\n totalChunks: chunkResult.segments.length\n }));\n} else {\n segments = input.segments;\n}\n\nconst items = segments.map(segment => ({\n json: {\n ...segment,\n kind: input.kind,\n jobId: input.jobId,\n totalSegments: segments.length,\n traite: input.traite,\n page: input.page,\n section: input.section,\n commentator: input.commentator,\n sourceLanguage: input.sourceLanguage,\n targetLanguage: input.targetLanguage,\n apiKey: input.apiKey,\n openaiApiKey: input.openaiApiKey,\n context: input.context,\n metadata: input.metadata,\n pipelineType: input.pipelineType\n }\n}));\n\nreturn items;"
},
"id": "prepare-segments",
"name": "Prepare Segments",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1984,
208
]
},
{
"parameters": {
"jsCode": "// Use original segments directly (no chunking)\nconst input = $('Parse Input').first().json;\n\nconst items = input.segments.map(segment => ({\n json: {\n ...segment,\n kind: input.kind,\n jobId: input.jobId,\n totalSegments: input.totalSegments,\n traite: input.traite,\n page: input.page,\n section: input.section,\n commentator: input.commentator,\n sourceLanguage: input.sourceLanguage,\n targetLanguage: input.targetLanguage,\n apiKey: input.apiKey,\n openaiApiKey: input.openaiApiKey,\n context: input.context,\n metadata: input.metadata,\n pipelineType: input.pipelineType\n }\n}));\n\nreturn items;"
},
"id": "prepare-segments-direct",
"name": "Prepare Segments Direct",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1760,
672
]
},
{
"parameters": {
"options": {}
},
"id": "loop-segments",
"name": "Loop Segments",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
2432,
208
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.N8N_WEBHOOK_URL || 'http://localhost:5678' }}/webhook/torah-translate",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ job_id: $json.jobId, segment_id: $json.segment_id, commentary_detail_id: $json.commentary_detail_id, kind: $json.kind, text: $json.text, reference_translation: $json.reference_translation, source_language: $json.sourceLanguage, target_language: $json.targetLanguage, api_key: $json.apiKey, context: { traite: $json.traite, page: $json.page, section: $json.section, commentator: $json.commentator }, metadata: $json.metadata }) }}",
"options": {
"timeout": 300000
}
},
"id": "call-translate-worker",
"name": "Call Translate Worker",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2640,
304
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"leftValue": "={{ $json.success }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "translate-success",
"name": "Translate Success?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
2864,
304
]
},
{
"parameters": {
"jsCode": "// Prepare save payload - route by kind, no source_text as identifier\nconst loopItem = $('Loop Segments').first().json;\nconst translateResult = $input.first().json;\n\n// Base payload - use correct ID field based on kind\nconst targetPayload = {\n jobId: loopItem.jobId,\n kind: loopItem.kind,\n segmentIndex: loopItem.index,\n totalSegments: loopItem.totalSegments,\n translation: translateResult.translation,\n // ID based on kind (mutually exclusive)\n segment_id: loopItem.kind === 'segment' ? loopItem.segment_id : null,\n commentary_detail_id: loopItem.kind === 'commentary' ? loopItem.commentary_detail_id : null,\n // NO source_text as identifier - it was causing ambiguous payload errors\n targetLanguage: loopItem.targetLanguage,\n provider: 'anthropic',\n model: 'claude-sonnet-4',\n metadata: loopItem.metadata,\n isChunk: loopItem.isChunk || false,\n pipelineType: loopItem.pipelineType,\n method: translateResult.method || 'unknown'\n};\n\n// Handle EN pivot generation\nconst hasGeneratedEnglish = translateResult.generated_english === true && translateResult.english_pivot;\n\nif (hasGeneratedEnglish) {\n const englishPayload = {\n ...targetPayload,\n translation: translateResult.english_pivot,\n targetLanguage: 'en',\n metadata: { ...loopItem.metadata, is_generated_pivot: true },\n pipelineType: 'en_pivot_generation',\n method: 'structured_generation',\n isEnglishPivot: true\n };\n \n return [\n { json: englishPayload },\n { json: { ...targetPayload, hasEnglishPivotSaved: true } }\n ];\n}\n\nreturn [{ json: targetPayload }];"
},
"id": "prepare-save",
"name": "Prepare Save",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3088,
208
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.N8N_WEBHOOK_URL || 'http://localhost:5678' }}/webhook/torah-save",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ kind: $json.kind, segment_id: $json.segment_id, commentary_detail_id: $json.commentary_detail_id, translated_text: $json.translation, target_language: $json.targetLanguage, provider: $json.provider || 'anthropic', model: $json.model || 'claude-sonnet-4', quality_score: $json.qualityScore, job_id: $json.jobId, request_id: $json.requestId, status: 'approved' }) }}",
"options": {
"timeout": 30000
}
},
"id": "call-save-worker",
"name": "Call Save Worker",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3312,
208
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.N8N_WEBHOOK_URL || 'http://localhost:5678' }}/webhook/torah-error",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ job_id: $('Loop Segments').first().json.jobId, segment_index: $('Loop Segments').first().json.index, worker: 'translate', error: { code: $json.error?.code || 'TRANSLATE_ERROR', message: $json.error?.message || 'Translation failed' }, context: { kind: $('Loop Segments').first().json.kind, text_preview: $('Loop Segments').first().json.text?.substring(0, 100), char_count: $('Loop Segments').first().json.char_count } }) }}",
"options": {
"timeout": 10000
}
},
"id": "call-error-handler",
"name": "Call Error Handler",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3088,
400
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// Update progress after each segment\nconst loopItem = $('Loop Segments').first().json;\nconst current = loopItem.index + 1;\nconst total = loopItem.totalSegments;\nconst percentage = Math.round((current / total) * 100);\n\nreturn [{\n json: {\n jobId: loopItem.jobId,\n current: current,\n total: total,\n percentage: percentage,\n isLast: current >= total\n }\n}];"
},
"id": "calc-progress",
"name": "Calc Progress",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3520,
304
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $env.API_URL }}/api/v2/jobs/{{ $json.jobId }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ status: 'processing', progress: { current: $json.current, total: $json.total, percentage: $json.percentage } }) }}",
"options": {
"timeout": 5000
}
},
"id": "update-progress",
"name": "Update Progress",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3744,
304
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $('Calc Progress').first().json.isLast }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "is-last",
"name": "Is Last?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
3968,
304
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $env.API_URL }}/api/v2/jobs/{{ $('Calc Progress').first().json.jobId }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ status: 'completed', progress: { current: $('Calc Progress').first().json.total, total: $('Calc Progress').first().json.total, percentage: 100 } }) }}",
"options": {
"timeout": 10000
}
},
"id": "set-completed",
"name": "Set Completed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
4192,
208
],
"onError": "continueRegularOutput"
},
{
"parameters": {},
"id": "done",
"name": "Done",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
4400,
304
]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Parse Input",
"type": "main",
"index": 0
}
]
]
},
"Parse Input": {
"main": [
[
{
"node": "Valid?",
"type": "main",
"index": 0
}
]
]
},
"Valid?": {
"main": [
[
{
"node": "Respond Accepted",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Rejected Payload",
"type": "main",
"index": 0
}
]
]
},
"Log Rejected Payload": {
"main": [
[
{
"node": "Respond Error",
"type": "main",
"index": 0
}
]
]
},
"Respond Accepted": {
"main": [
[
{
"node": "Needs Job Creation?",
"type": "main",
"index": 0
}
]
]
},
"Needs Job Creation?": {
"main": [
[
{
"node": "Create Job",
"type": "main",
"index": 0
}
],
[
{
"node": "Set Processing",
"type": "main",
"index": 0
}
]
]
},
"Create Job": {
"main": [
[
{
"node": "Set Processing",
"type": "main",
"index": 0
}
]
]
},
"Set Processing": {
"main": [
[
{
"node": "Needs Chunking?",
"type": "main",
"index": 0
}
]
]
},
"Needs Chunking?": {
"main": [
[
{
"node": "Call Chunk Worker",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Segments Direct",
"type": "main",
"index": 0
}
]
]
},
"Call Chunk Worker": {
"main": [
[
{
"node": "Prepare Segments",
"type": "main",
"index": 0
}
]
]
},
"Prepare Segments": {
"main": [
[
{
"node": "Loop Segments",
"type": "main",
"index": 0
}
]
]
},
"Prepare Segments Direct": {
"main": [
[
{
"node": "Loop Segments",
"type": "main",
"index": 0
}
]
]
},
"Loop Segments": {
"main": [
[
{
"node": "Done",
"type": "main",
"index": 0
}
],
[
{
"node": "Call Translate Worker",
"type": "main",
"index": 0
}
]
]
},
"Call Translate Worker": {
"main": [
[
{
"node": "Translate Success?",
"type": "main",
"index": 0
}
]
]
},
"Translate Success?": {
"main": [
[
{
"node": "Prepare Save",
"type": "main",
"index": 0
}
],
[
{
"node": "Call Error Handler",
"type": "main",
"index": 0
}
]
]
},
"Prepare Save": {
"main": [
[
{
"node": "Call Save Worker",
"type": "main",
"index": 0
}
]
]
},
"Call Save Worker": {
"main": [
[
{
"node": "Calc Progress",
"type": "main",
"index": 0
}
]
]
},
"Call Error Handler": {
"main": [
[
{
"node": "Calc Progress",
"type": "main",
"index": 0
}
]
]
},
"Calc Progress": {
"main": [
[
{
"node": "Update Progress",
"type": "main",
"index": 0
}
]
]
},
"Update Progress": {
"main": [
[
{
"node": "Is Last?",
"type": "main",
"index": 0
}
]
]
},
"Is Last?": {
"main": [
[
{
"node": "Set Completed",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Segments",
"type": "main",
"index": 0
}
]
]
},
"Set Completed": {
"main": [
[
{
"node": "Done",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner"
},
"staticData": null,
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Torah Router. Uses httpRequest. Webhook trigger; 25 nodes.
Source: https://github.com/fsebbah/n8n-workflows/blob/cfce483f81a240422fd048e1c680c746b877429a/workflows/Torah_Router.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c
Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.