This workflow corresponds to n8n.io template #14487 — we link there as the canonical source.
This workflow follows the Datatable → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "709a58d2-d2fe-422d-bb31-7c7ae04af1c3",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
368
],
"parameters": {
"width": 480,
"height": 800,
"content": "## Notify on menu orders via ntfy and Home Assistant TTS with daily BAC tracking\n\n\ud83d\udcd6 [Full documentation](https://paoloronco.notion.site/Documentation-Menu-Order-Push-Notifications-Home-Assistant-TTS-BAC-32ff0ba27c328075a886d89ebfbf5ce5)\n\n### How it works\n\n1. Receives a menu order through a webhook (POST body: `name`, `time`, `items[]`).\n2. Normalizes the customer name and formats the order string.\n3. Logs the order to a DataTable (item, person, alcohol_grams, date, order_time).\n4. Reads all of today's orders for that person from the DataTable.\n5. Calculates a cumulative BAC estimate using the Widmark formula.\n6. Sends a push notification via ntfy and announces the order via Home Assistant TTS.\n\n### Setup steps\n\n- [ ] Create a DataTable node with columns: `item`, `person`, `alcohol_grams`, `date`, `order_time`\n- [ ] Set up ntfy credentials (HTTP Header Auth) in the **Send Ntfy Notification** node\n- [ ] Set the ntfy URL to your server/topic (e.g. `https://ntfy.sh/your-topic`)\n- [ ] Set up Home Assistant credentials and update the script service name\n- [ ] Adjust the Widmark formula weight/factor if needed (default: 70 kg, 0.68)\n\n### Customization\n\nThe BAC thresholds, weight constant, and notification message format can all be adjusted in the **Calculate Cumulative BAC** node."
},
"typeVersion": 1
},
{
"id": "9e8f77e4-7dcb-4647-a5e2-fb00028c5b16",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1648,
368
],
"parameters": {
"color": 7,
"width": 432,
"height": 304,
"content": "## Receive and prepare orders\n\nReceives the menu order via a webhook and prepares the data by normalizing and calculating necessary order information."
},
"typeVersion": 1
},
{
"id": "067991b5-23c9-4174-b9a5-6b6bb6982808",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2096,
368
],
"parameters": {
"color": 7,
"width": 432,
"height": 304,
"content": "## Log order and read data\n\nLogs the prepared order into the database and reads the accumulated orders for the day."
},
"typeVersion": 1
},
{
"id": "efea2242-1257-4f84-ac0b-1c8d1281d491",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2560,
368
],
"parameters": {
"color": 7,
"width": 672,
"height": 304,
"content": "## Calculate BAC and notify\n\nCalculates the cumulative BAC from today's orders and sends notifications followed by TTS announcements."
},
"typeVersion": 1
},
{
"id": "817f6c00-fbfd-4d4f-b0cf-0af68bc32893",
"name": "When Order Received",
"type": "n8n-nodes-base.webhook",
"position": [
1696,
496
],
"parameters": {
"path": "menu",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "a19b54cd-144d-4fd1-b0dc-0aa34f25a972",
"name": "Calculate Alcohol and Format Order",
"type": "n8n-nodes-base.code",
"position": [
1936,
496
],
"parameters": {
"jsCode": "// Normalize name, calculate alcohol amount and order string for current order\nconst body = $input.first().json.body;\nconst rawName = (body.name || 'Anonymous').trim();\nconst name = rawName.charAt(0).toUpperCase() + rawName.slice(1).toLowerCase();\nconst orderTime = body.time || new Date().toLocaleString();\nconst items = body.items || [];\nconst today = new Date().toISOString().slice(0, 10);\n\nconst alcoholGrams = items.reduce((s, i) => s + (i.alcohol_grams || 0) * (i.quantity || 1), 0);\n\nconst orderStr = items\n .map(i => {\n let s = i.quantity + 'x ' + i.name;\n if (i.detail) s += ' ' + i.detail;\n return s;\n })\n .join(', ');\n\nreturn [{ json: { name, orderTime, items, orderStr, alcoholGrams: Math.round(alcoholGrams * 10) / 10, today } }];"
},
"typeVersion": 2
},
{
"id": "114d6f2c-17f4-422b-b6d9-6072b6b3e9aa",
"name": "Log Order to Database",
"type": "n8n-nodes-base.dataTable",
"position": [
2144,
496
],
"parameters": {
"columns": {
"value": {
"date": "={{ $json.today }}",
"item": "={{ $json.orderStr }}",
"person": "={{ $json.name }}",
"order_time": "={{ $json.orderTime }}",
"alcohol_grams": "={{ $json.alcoholGrams }}"
},
"schema": [
{
"id": "item",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "item",
"defaultMatch": false
},
{
"id": "person",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "person",
"defaultMatch": false
},
{
"id": "alcohol_grams",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "alcohol_grams",
"defaultMatch": false
},
{
"id": "date",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "date",
"defaultMatch": false
},
{
"id": "order_time",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "order_time",
"defaultMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"typeVersion": 1.1
},
{
"id": "52f52c12-7940-4364-9b77-1fa63ecbd2c5",
"name": "Read Today's Orders",
"type": "n8n-nodes-base.dataTable",
"position": [
2384,
496
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "person",
"keyValue": "={{ $('Calculate Alcohol and Format Order').item.json.name }}"
},
{
"keyName": "date",
"keyValue": "={{ $('Calculate Alcohol and Format Order').item.json.today }}"
}
]
},
"matchType": "allConditions",
"operation": "get",
"returnAll": true,
"dataTableId": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"typeVersion": 1.1
},
{
"id": "634dc78b-3e39-4b2e-b030-ba4c734f7548",
"name": "Calculate Cumulative BAC",
"type": "n8n-nodes-base.code",
"position": [
2608,
496
],
"parameters": {
"jsCode": "// Calculate cumulative BAC by reading today's rows from the DataTable\n// rows = all orders for this person today (including the one just inserted)\nconst rows = $input.all().map(i => i.json);\nconst name = $('Calculate Alcohol and Format Order').item.json.name;\nconst orderTime = $('Calculate Alcohol and Format Order').item.json.orderTime;\nconst orderStr = $('Calculate Alcohol and Format Order').item.json.orderStr;\n\nconst totalAlcohol = Math.round(rows.reduce((s, r) => s + (r.alcohol_grams || 0), 0) * 10) / 10;\nconst orderCount = rows.length;\nconst bac = totalAlcohol > 0 ? totalAlcohol / (70 * 0.68) : 0;\nconst bacStr = bac.toFixed(2);\n\nconst E_OK = String.fromCodePoint(0x1F7E2);\nconst E_WARN = String.fromCodePoint(0x1F7E1);\nconst E_OVER = String.fromCodePoint(0x1F534);\nconst E_NONE = String.fromCodePoint(0x2B1C);\nconst E_LIST = String.fromCodePoint(0x1F4CB);\nconst E_CLOCK = String.fromCodePoint(0x1F552);\nconst E_DRINK = String.fromCodePoint(0x1F378);\nconst E_WATER = String.fromCodePoint(0x1F964);\nconst E_TIME = String.fromCodePoint(0x1F550);\n\nlet bacLevel;\nif (bac === 0) bacLevel = E_NONE;\nelse if (bac < 0.2) bacLevel = E_OK;\nelse if (bac < 0.5) bacLevel = E_WARN;\nelse bacLevel = E_OVER;\n\n// Previous orders = all rows except the last one (just inserted)\nconst prevOrders = rows.slice(0, -1);\nconst historyStr = prevOrders.length > 0\n ? '\\n' + E_LIST + ' Already ordered today (' + prevOrders.length + ' orders):\\n'\n + prevOrders.map(r => ' - ' + r.item + ' ' + E_CLOCK + ' ' + r.order_time).join('\\n')\n : '';\n\nconst bacRow = totalAlcohol > 0\n ? '\\n' + E_DRINK + ' BAC ~' + bacStr + ' g/L ' + bacLevel\n : '\\n' + E_WATER + ' no alcohol';\n\nconst message = name + ': ' + orderStr\n + '\\n' + E_TIME + ' ' + orderTime\n + historyStr\n + bacRow;\n\nreturn [{\n json: {\n name,\n orderTime,\n orderStr,\n message,\n bac: bacStr,\n bacLevel,\n totalAlcohol,\n alcoholGrams: $('Calculate Alcohol and Format Order').item.json.alcoholGrams,\n orderCount,\n today: $('Calculate Alcohol and Format Order').item.json.today\n }\n}];"
},
"typeVersion": 2
},
{
"id": "929d8f78-ead4-4545-8e45-77c32b8a5d16",
"name": "Send Ntfy Notification",
"type": "n8n-nodes-base.httpRequest",
"position": [
2848,
496
],
"parameters": {
"url": "http://YOUR_NTFY_SERVER/YOUR_TOPIC",
"body": "={{ $json.message }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"authentication": "genericCredentialType",
"rawContentType": "text/plain",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.4
},
{
"id": "e1accf02-8716-4f2e-ae39-9b1dbc6bd392",
"name": "Announce Order with Home Assistant",
"type": "n8n-nodes-base.homeAssistant",
"position": [
3088,
496
],
"parameters": {
"domain": "script",
"service": "announce_order",
"resource": "service",
"operation": "call",
"serviceAttributes": {
"attributes": [
{
"name": "message",
"value": "={{ $json.name }} HAS ORDERED {{ $('When Order Received').item.json.body.items[0].name }}"
}
]
}
},
"typeVersion": 1
}
],
"connections": {
"Read Today's Orders": {
"main": [
[
{
"node": "Calculate Cumulative BAC",
"type": "main",
"index": 0
}
]
]
},
"When Order Received": {
"main": [
[
{
"node": "Calculate Alcohol and Format Order",
"type": "main",
"index": 0
}
]
]
},
"Log Order to Database": {
"main": [
[
{
"node": "Read Today's Orders",
"type": "main",
"index": 0
}
]
]
},
"Send Ntfy Notification": {
"main": [
[
{
"node": "Announce Order with Home Assistant",
"type": "main",
"index": 0
}
]
]
},
"Calculate Cumulative BAC": {
"main": [
[
{
"node": "Send Ntfy Notification",
"type": "main",
"index": 0
}
]
]
},
"Calculate Alcohol and Format Order": {
"main": [
[
{
"node": "Log Order to Database",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Receive instant push notifications on your phone and voice announcements on your Google Home every time someone orders from your intranet menu — with cumulative BAC tracking per person.
Source: https://n8n.io/workflows/14487/ — 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.
Response_Handler. Uses httpRequest, dataTable, n8n-nodes-evolution-api, redis. Webhook trigger; 32 nodes.
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 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.