De lijm die alles verbindt
n8n is de centrale workflow automation engine van ASD-PMA. Het verbindt alle services, automatiseert repetitieve taken en zorgt ervoor dat data naadloos stroomt tussen systemen.
| Type | Trigger | Voorbeeld |
|---|---|---|
| Scheduled | Cron/interval | Health checks elke 5 min |
| Webhook | HTTP request | Slack command verwerking |
| Event-driven | Service event | Nieuwe ticket naar notificatie |
| Manual | Button click | Data migratie |
Escaleer support tickets naar development:
Trigger: @ticket prefix in Zammad internal note
Universal reply naar alle services:
Proactieve service monitoring:
Monitored Services:
/api/v1/users/me//api/method/ping/api/v4/system/ping/health// n8n Code Node - API request
const response = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/resource',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
json: true
});
return [{ json: response }];
// Transform Zammad ticket to Redmine issue
const ticket = $input.first().json;
const redmineIssue = {
issue: {
project_id: 1,
tracker_id: 1,
subject: ticket.title,
description: `
Source: Zammad ZD#${ticket.number}
Customer: ${ticket.customer}
---
${ticket.article.body}
`,
priority_id: mapPriority(ticket.priority)
}
};
return [{ json: redmineIssue }];
function mapPriority(zammadPriority) {
const map = {
'1 high': 5,
'2 normal': 4,
'3 low': 3
};
return map[zammadPriority] || 4;
}
// Route based on ticket type
const ticket = $input.first().json;
if (ticket.group === 'Technical') {
return [{ json: { target: 'redmine', ...ticket }}];
} else if (ticket.group === 'Sales') {
return [{ json: { target: 'erpnext', ...ticket }}];
} else {
return [{ json: { target: 'mattermost', ...ticket }}];
}
// Graceful error handling
try {
const result = await this.helpers.httpRequest({
method: 'POST',
url: 'https://api.example.com/resource',
body: data
});
return [{ json: { success: true, data: result }}];
} catch (error) {
// Log error but don't fail workflow
console.error('API call failed:', error.message);
// Send alert
await this.helpers.httpRequest({
method: 'POST',
url: 'https://mattermost.example.com/hooks/xxx',
body: {
text: 'Workflow error: ' + error.message
}
});
return [{ json: { success: false, error: error.message }}];
}
# Export workflow
curl "https://n8n.example.com/webhook/export-workflow?name=My%20Workflow" \
-o workflow.json
# Import workflow
curl -X POST "https://n8n.example.com/webhook/import-etl" \
-H "Content-Type: application/json" \
-d @workflow.json
# Toggle workflow
curl -X POST "https://n8n.example.com/webhook/workflow-toggle" \
-H "Content-Type: application/json" \
-d '{"workflow_name": "My Workflow", "active": true}'
| Workflow | Type | Schedule | Status |
|---|---|---|---|
| Zammad Note to Redmine | Integration | 10s poll | Active |
| Mattermost Reply Handler | Webhook | On-demand | Active |
| Health Check Monitor | Scheduled | 5 min | Active |
| Daily Metrics Report | Scheduled | 08:00 | Active |
| Service | Auth Type | Storage |
|---|---|---|
| Zammad | Token | n8n credentials |
| Redmine | API Key | n8n credentials |
| ERPNext | Token | n8n credentials |
| Mattermost | Token | n8n credentials |
Credential Hierarchy:
# Get latest error details
curl -X POST "https://n8n.example.com/webhook/latest-error-details" \
-H "Content-Type: application/json" \
-d '{"workflow_name": "My Workflow"}'
| Issue | Cause | Solution |
|---|---|---|
| Webhook not triggering | Workflow inactive | Toggle workflow on |
| 401 errors | Token expired | Refresh credentials |
| Data not syncing | Schedule paused | Check workflow status |
| Memory errors | Large dataset | Add pagination |
# View n8n logs
docker logs asd-n8n --tail 100
# Filter for errors
docker logs asd-n8n 2>&1 | grep -i error
# Watch real-time
docker logs asd-n8n -f
| Aspect | Polling | Webhooks |
|---|---|---|
| Trigger | Check every X seconds | Instant trigger |
| Resources | Higher usage | Lower usage |
| Latency | Delay up to X seconds | Real-time |
Recommendation: Prefer webhooks where available.
// Process items in batches
const items = $input.all();
const batchSize = 10;
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await processBatch(batch);
results.push(...batchResults);
// Small delay between batches
await new Promise(r => setTimeout(r, 100));
}
return results.map(r => ({ json: r }));
// Use staticData to track processed items
const staticData = $getWorkflowStaticData('global');
if (!staticData.processedIds) {
staticData.processedIds = [];
}
// Prevent duplicate processing
const newItems = items.filter(
item => !staticData.processedIds.includes(item.id)
);
// Track processed
newItems.forEach(item => {
staticData.processedIds.push(item.id);
});
// Limit memory usage
if (staticData.processedIds.length > 1000) {
staticData.processedIds = staticData.processedIds.slice(-500);
}
{
"name": "Service A to Service B",
"nodes": [
{
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": { "interval": [{ "field": "seconds", "secondsInterval": 30 }] }
}
},
{
"name": "Fetch from A",
"type": "n8n-nodes-base.httpRequest"
},
{
"name": "Transform",
"type": "n8n-nodes-base.code"
},
{
"name": "Post to B",
"type": "n8n-nodes-base.httpRequest"
},
{
"name": "Notify on Error",
"type": "n8n-nodes-base.httpRequest"
}
]
}
{
"name": "Webhook Handler",
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "my-endpoint",
"httpMethod": "POST"
}
},
{
"name": "Validate Input",
"type": "n8n-nodes-base.code"
},
{
"name": "Process",
"type": "n8n-nodes-base.code"
},
{
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook"
}
]
}
| Level | Access |
|---|---|
| Owner | Full workflow access |
| Editor | Create/edit workflows |
| Viewer | View only |
// Validate webhook signature
const signature = $request.headers['x-signature'];
const payload = JSON.stringify($input.first().json);
const secret = 'your-secret';
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expected) {
throw new Error('Invalid signature');
}