n8n EU AI Act guard: AI notices, HMAC digests and approval links
n8n-nodes-spurvik is an MIT community node for n8n running in your own stack. It adds one node, Spurvik AI Act Guard (type id spurvikEuAiActGuard), with three operations:
- Add an AI interaction notice: puts a notice in front of the AI output text, to support your Art. 50(1) notice where an n8n workflow answers people.
- Record a keyed digest (shown in n8n as
Record an HMAC-SHA256 digest): records a keyed digest of the input field instead of the raw prompt text. - Send an ask_human() approval request: sends signed Approve / Block links to a reviewer before the workflow does something risky.
By default, when the optional Spurvik Console API Key is left empty, the node runs locally and does not call the Spurvik HTTP API, so it needs no console account. Set that key on the credential to sync to the hosted console at the node's Console Base URL setting (default https://console.spurvik.com). It does not mark content.
Only for n8n in your own stack. The node has not been reviewed by n8n for n8n Cloud, so install it on an instance you run yourself. It was tested against the n8n-workflow 2.46.4 library.
Install
In n8n: Settings → Community nodes → Install, then enter:
n8n-nodes-spurvik@1.0.4Or with npm in your n8n custom nodes folder:
npm i n8n-nodes-spurvik@1.0.4Import the starter workflow in 10 seconds
In the n8n workflow editor, open the ⋯ menu → Import from URL and paste the URL below (or download n8n-art50-starter.json). It wires all three operations from one sample AI item — a parallel HMAC-SHA256 input digest branch (no prompt text in the node output) and an Art. 50(1) AI notice on outputText followed by conditional Slack approval links — with optional fra1 Console sync (Free €0 for 10k events/mo · Pro €19/mo).
https://spurvik.com/r/n8n-art50-starter.jsonCredential: spurvikApi
Create a Spurvik credential. It holds:
- Signing Secret (required): at least 16 characters, no leading or trailing whitespace. The node throws otherwise. It is used for every digest and every approval link.
- Spurvik Console API Key (optional): leave empty to run locally. When set, the node can send hashed receipts, disclosure sessions or approval rows to the hosted console at the node's Console Base URL setting (default https://console.spurvik.com).
- Channel tokens as password fields, only for the channels you use: Slack, Discord or Teams webhook URL; Telegram bot token; Resend API key (Email); Linear API key; Jira API token; Monday.com API token; Asana access token.
Operation 1: add an AI interaction notice
| Field | Default | What it does |
|---|---|---|
| AI System Name | AI system | Name used in the notice |
| Output Field | outputText | Field on the incoming item that holds the AI output |
| Notice Text | empty | Your own wording; empty uses the default |
Default notice: "You are interacting with an AI system (‹name›). This notice is designed to support transparency about that interaction." The node returns it as spurvik.art50Notice and the notice plus the output as spurvik.disclosedOutputText.
The node refuses an Output Field that equals the Input Field or is named promptText, prompt, chatInput, query or input, so the notice lands on the AI output, not the prompt.
Operation 2: record a keyed digest
The node reads the Input Field (default promptText) and returns a record under spurvik:
inputHmac: HMAC SHA256 ofspurvik:workflow:input:v1|plus the input field, keyed with your signing secretpreviousRecordHash: the incoming item'spreviousRecordHash, or 64 zerosoutputHmac: alwaysnullin this operation, because the node passes only the input field to the digestrecordHash: HMAC SHA256, keyed with your signing secret, overspurvik:workflow:chain:v1|plus the previous hash, system name,inputHmac,outputHmac(or the literalnonewhen it is null, as it always is from this node) and timestamp, so you can chain recordstimestampIso
Prompt field names and the configured Input Field are never copied onto the output, including when listed in the Pass-through Fields setting.
Mind n8n's own execution data. The node's output holds no prompt text, but n8n stores each node's input on the execution. Set your retention yourself with EXECUTIONS_DATA_SAVE_ON_SUCCESS, EXECUTIONS_DATA_SAVE_ON_ERROR, EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS, EXECUTIONS_DATA_PRUNE, EXECUTIONS_DATA_MAX_AGE and EXECUTIONS_DATA_PRUNE_MAX_COUNT.
Operation 3: send an ask_human() approval request
| Field | Notes |
|---|---|
| Channel | Slack, Telegram, Email (Resend), Discord, Microsoft Teams, Linear, Jira, Monday.com, Asana |
| Approver Target | Chat id, recipient, team id, project key, board id or project gid (not needed for Slack, Discord, Teams) |
| Callback URL | Required. A URL you run that accepts GET clicks on the links |
| Action Name / Action Summary | What the reviewer sees. Keep personal data out of the summary |
The node signs separate Approve and Block links. Each link carries the gate id, a timestamp, an action HMAC, a random nonce and a signature. It returns approveUrl, rejectUrl, gateId and the channel receipt.
Monday.com only creates a board item. It carries the gate id but no Approve or Block links, so the reviewer needs another way to decide.
There is no hosted handler. The links point at your Callback URL. Something you run must check the click with verifyChannelApprovalToken() from spurvik-sdk/channels, record the nonce and refuse a repeat, and let the action continue only on approved. A common pattern:
- An n8n Webhook node receives the GET click:
/webhook/spurvik-gate?gateId=…&ts=…&hmac=…&nonce=…&decision=approved&sig=… - An n8n HTTP Request node posts those values to your service.
- Your service returns the result of the check; the workflow continues only when it is true and the decision is
approved.
// your-service/verify.ts (spurvik-sdk@1.0.8)
import { verifyChannelApprovalToken } from "spurvik-sdk/channels";
export function checkClick(q: URLSearchParams) {
const gateId = q.get("gateId");
const decision = q.get("decision");
const nonce = q.get("nonce");
if (!gateId || !nonce || (decision !== "approved" && decision !== "rejected")) {
return { valid: false, approved: false, gateId, nonce };
}
const valid = verifyChannelApprovalToken({
gateId,
actionHmac: q.get("hmac") ?? "",
decision,
issuedAtUnix: Number(q.get("ts")),
signature: q.get("sig") ?? "",
nonce,
signingSecret: process.env.SPURVIK_SIGNING_SECRET!, // same secret as the n8n credential
});
// Record the nonce and refuse a repeat before you act on the result.
return { valid, approved: valid && decision === "approved", gateId, nonce };
}Links expire after 24 hours by default (maxAgeSeconds). Until then a link can be reused, which is why you record the nonce.
The SDK and MCP server are MIT and run in your own stack.
Try it
n8n-nodes-spurvik@1.0.4<script src="https://spurvik.com/badge.js" data-system="Your AI" data-attribution="true" defer></script>- A small “Art. 50 · Spurvik” link shows after the notice by default. On Pro (€19/mo) you can hide it with
data-attribution="false"for a white-label badge.
Free €0 (10k events/mo) · Pro €19/mo · Team €79/mo · excl. VAT · B2B only.
FAQ
Does the node work on n8n Cloud?
It is built for n8n running in your own stack. n8n has not reviewed it for n8n Cloud.
Do I need a Spurvik account or API key?
No account is required. By default the node does not call the Spurvik API and needs only the signing secret and the tokens for the channels you use. Set the optional console API key only if you want the node to sync to the hosted console.
Does the node store my prompts?
The node output does not copy prompt text or the configured input field. n8n itself keeps each node's input on the execution, so set n8n's execution data retention.
Who checks the Approve and Block clicks?
You do. The links go to your Callback URL, and your service checks them with verifyChannelApprovalToken() from the channels export of the Spurvik SDK. There is no hosted handler.
Does Monday.com get Approve and Block links?
No. Monday.com only gets a board item with the gate id.
Does the node mark content generated by AI?
No. It adds a visible notice to text and records digests; it is not Art. 50(2) marking in a machine readable format.
Sources
- npm: https://www.npmjs.com/package/n8n-nodes-spurvik (1.0.4; package README and
dist/inspected 11 Nov 2026) - Source (MIT): https://github.com/spurvik/spurvik/tree/main/packages/n8n-nodes-spurvik
- Article 50 dates: https://spurvik.com/art50-deadline-checklist
Reviewed 11 November 2026. Not legal advice. Supports parts of your Art. 50 work; responsibility stays with you. EyeTea Solutions ApS · Trekantområdet, Denmark