n8n community node

The document-sharing node for people who read the source

n8n-nodes-everypage turns any PDF in your workflow into a tracked share link: binary in, share URL + QR + embed out, instant HMAC-verified triggers the moment documents are read - and AI Agent nodes can drive the whole thing as a typed tool.

How the node works.

One node, three resources - File, Link Variant, and Event - plus a trigger node that subscribes to reader activity. The field names match the EveryPage Zapier and Make apps, so workflows translate across platforms without re-learning.

  1. 1

    Install the community node

    On self-hosted n8n: Settings -> Community Nodes -> install n8n-nodes-everypage (or npm install it into ~/.n8n/nodes). The package has zero runtime dependencies - a verified-community-node requirement - so what you install is exactly what you can read.

  2. 2

    Connect with an API key

    Create a key on your account page (Account -> API keys - it starts with ep_live_) and paste it into a new EveryPage API credential. The node validates it against the API immediately, and the Base URL field only changes when you point at a staging deployment.

  3. 3

    Wire it into the flow

    Hand the node binary data from any previous step and it returns the share trio - link, QR code, embed code - for the steps that follow. Activate a trigger and the webhook subscription registers itself; deactivate and it is removed. Nothing to configure on a server.

Under the hood

Every delivery verified

Instant triggers arrive over HMAC-SHA256-signed webhooks, and the node verifies every one - constant-time signature comparison with a five-minute replay tolerance - before your workflow runs. Unverified deliveries are rejected with a 401.

Binary-native

PDFs travel as n8n binary data - no upload-by-URL dance. An agent, a Code node, or a download step hands over bytes; the QR code comes back the same way, as a PNG ready for print and merge workflows.

Built for self-hosters

A polling fallback with independent per-stream cursors covers instances that cannot receive inbound webhooks, the MIT-licensed source means you can read exactly what runs, and the multipart and HMAC plumbing uses nothing but Node built-ins.

Analytics your DPO will wave through

Readership is measured without cookies and reader IP addresses are never stored - each reader gets a salted, pseudonymous identity instead. If you self-host your workflows because you care where data lives, this is the same instinct applied to document analytics.

Triggers: when this happens in EveryPage.

The EveryPage Trigger node subscribes to nine event kinds over signed webhooks - pushed the moment they happen - with a polling fallback for closed networks. Optionally scope any subscription to a single document.

Document Read

Instant

A reader finished viewing a document - with pages viewed, time spent, country, and link-variant attribution.

Download

Instant

A reader (not you) saved a copy. Log it, score it, or follow up on it.

Form Fill / Email Gate Capture

Instant · Pro

A viewer completed the email gate or lead form - every captured field arrives ready to shape and forward, while the lead is still reading.

Comments & receipts

Instant

A reader left feedback on the document or confirmed receipt of it - feedback routed while they are still on the page, proof of delivery for the audit trail.

Lifecycle events

Instant

A link burned out its view budget, content was replaced behind a link, an invite was viewed, a proofing selection changed - the housekeeping events that keep downstream systems honest.

Polling mode

Fallback

For instances that cannot receive inbound webhooks: walks the events feed on your schedule with independent per-stream cursors and namespaced event IDs - nothing fires twice, nothing is missed.

Operations: one node, the whole API.

File, Link Variant, and Event resources with typed, plan-aware fields. Every file-producing operation outputs the share trio - link, QR URL, embed code - keyed to durable identifiers, never a renameable slug.

Upload

Binary PDF in, tracked link out - with the full plan-tiered settings surface: viewer mode, passcode, expiry or never-expire, email gate, vanity slug, watermark, view limit, page range, protection toggles, notifications.

Import From URL

The node fetches a PDF from a URL itself - without your EveryPage credentials attached - and uploads it.

Get / Get Many / Update Settings

Fetch one document or list them all (UUID or short ID), and change any subset of settings in place - only the options you add are sent.

Replace Content

Pro

Swap the PDF behind a link - UUID, short ID, slug, QR, settings, and readership history all survive.

Get QR Code

The tracked QR code as a binary PNG, ready for print and merge workflows.

Get Readership

The plan-shaped analytics report - summary, funnel, sessions, contacts - as JSON for the next node.

Link Variants

Pro

Create, list, update, revoke, and delete per-recipient tracked links - one document, per-person readership, GDPR label redaction included.

Events: Get Many

Bulk pulls from the events feed - view, download, gate - with a since cursor and Return All paging, built for incremental warehouse loads.

Delete

Trash by default - restorable until the purge date, receipt included - or purge permanently.

Give your agent a document it can prove was read.

The node ships usableAsTool, so n8n AI Agent nodes can call EveryPage directly as a typed tool: "upload this report and give me the tracked link", "who read the Q3 proposal?". An agent that publishes its work as a tracked link - instead of an attachment - is an agent whose output you can audit.

Schedule → AI Agent → Code → EveryPage → Slack

Import this workflow: AI report → tracked link → Slack

Every Monday an AI agent writes the weekly report, a dependency-free Code node renders it to PDF, EveryPage returns a tracked link, and Slack gets the link - so you find out whether anyone read what the agent produced. Copy the JSON, then paste it straight onto the n8n canvas (Ctrl/Cmd-V), or use Workflow -> Import from Clipboard.

{
	"name": "AI report → EveryPage tracked link",
	"nodes": [
		{
			"parameters": {
				"content": "## AI report → tracked link\nEvery Monday an AI agent writes the weekly ops report, a Code node renders it to a PDF (no dependencies), and EveryPage turns it into a **tracked share link** — so you know whether anyone actually read what the agent produced.\n\nSwap the Slack step for email, Teams, or your CRM. Add `requireEmail: true` in the EveryPage node's options to gate the report (Pro).",
				"height": 260,
				"width": 420
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000001",
			"name": "Sticky Note",
			"type": "n8n-nodes-base.stickyNote",
			"typeVersion": 1,
			"position": [-220, -180]
		},
		{
			"parameters": {
				"rule": {
					"interval": [{ "field": "weeks", "triggerAtDay": [1], "triggerAtHour": 8 }]
				}
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000002",
			"name": "Every Monday 08:00",
			"type": "n8n-nodes-base.scheduleTrigger",
			"typeVersion": 1.2,
			"position": [-220, 60]
		},
		{
			"parameters": {
				"promptType": "define",
				"text": "Write this week's operations report for the team: a short executive summary, then sections for shipped work, metrics, risks, and next week's focus. Plain text, no markdown syntax. Around 500 words.",
				"options": {}
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000003",
			"name": "AI Agent",
			"type": "@n8n/n8n-nodes-langchain.agent",
			"typeVersion": 1.7,
			"position": [0, 60]
		},
		{
			"parameters": {
				"model": { "__rl": true, "value": "gpt-4.1-mini", "mode": "list" },
				"options": {}
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000004",
			"name": "OpenAI Chat Model",
			"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
			"typeVersion": 1.2,
			"position": [0, 260]
		},
		{
			"parameters": {
				"mode": "runOnceForAllItems",
				"jsCode": "// Render plain text to a minimal, valid, multi-page PDF - no dependencies.\nconst esc = (s) => s.replace(/\\\\/g, '\\\\\\\\').replace(/\\(/g, '\\\\(').replace(/\\)/g, '\\\\)');\nconst text = String($input.first().json.output ?? $input.first().json.text ?? 'Empty report');\n\nconst lines = [];\nfor (const raw of text.split('\\n')) {\n  let line = raw.replace(/[^\\x20-\\x7e]/g, '');\n  if (line === '') { lines.push(''); continue; }\n  while (line.length > 92) { lines.push(line.slice(0, 92)); line = line.slice(92); }\n  lines.push(line);\n}\nconst pages = [];\nfor (let i = 0; i < lines.length; i += 46) pages.push(lines.slice(i, i + 46));\nif (!pages.length) pages.push(['(empty)']);\n\nconst pageCount = pages.length;\nconst fontObj = 3 + pageCount * 2;\nconst objects = [];\nobjects[1] = '<< /Type /Catalog /Pages 2 0 R >>';\nobjects[2] = `<< /Type /Pages /Kids [${pages.map((_, p) => `${3 + p * 2} 0 R`).join(' ')}] /Count ${pageCount} >>`;\npages.forEach((pg, p) => {\n  const contentObj = 4 + p * 2;\n  objects[3 + p * 2] = `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 ${fontObj} 0 R >> >> /Contents ${contentObj} 0 R >>`;\n  let stream = 'BT /F1 11 Tf 56 780 Td 16 TL\\n';\n  for (const line of pg) stream += `(${esc(line)}) Tj T*\\n`;\n  stream += 'ET';\n  objects[contentObj] = `<< /Length ${stream.length} >>\\nstream\\n${stream}\\nendstream`;\n});\nobjects[fontObj] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>';\n\nlet pdf = '%PDF-1.4\\n';\nconst offsets = [];\nfor (let n = 1; n <= fontObj; n++) {\n  offsets[n] = pdf.length;\n  pdf += `${n} 0 obj\\n${objects[n]}\\nendobj\\n`;\n}\nconst xref = pdf.length;\npdf += `xref\\n0 ${fontObj + 1}\\n0000000000 65535 f \\n`;\nfor (let n = 1; n <= fontObj; n++) pdf += `${String(offsets[n]).padStart(10, '0')} 00000 n \\n`;\npdf += `trailer\\n<< /Size ${fontObj + 1} /Root 1 0 R >>\\nstartxref\\n${xref}\\n%%EOF`;\n\nconst fileName = `weekly-report-${new Date().toISOString().slice(0, 10)}.pdf`;\nconst binaryData = await this.helpers.prepareBinaryData(Buffer.from(pdf, 'latin1'), fileName, 'application/pdf');\nreturn [{ json: { fileName }, binary: { data: binaryData } }];"
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000005",
			"name": "Render PDF",
			"type": "n8n-nodes-base.code",
			"typeVersion": 2,
			"position": [280, 60]
		},
		{
			"parameters": {
				"resource": "file",
				"operation": "upload",
				"binaryPropertyName": "data",
				"fileName": "={{ $json.fileName }}",
				"options": {
					"viewerMode": "flipbook",
					"notifyOnView": true,
					"expiryHours": 168
				}
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000006",
			"name": "EveryPage",
			"type": "n8n-nodes-everypage.everyPage",
			"typeVersion": 1,
			"position": [560, 60]
		},
		{
			"parameters": {
				"select": "channel",
				"text": "=📄 This week's report is live: {{ $json.shareUrl }}\nEveryPage will tell us who actually reads it.",
				"otherOptions": {}
			},
			"id": "a1b2c3d4-0000-4000-8000-000000000007",
			"name": "Post link to Slack",
			"type": "n8n-nodes-base.slack",
			"typeVersion": 2.2,
			"position": [840, 60]
		}
	],
	"connections": {
		"Every Monday 08:00": { "main": [[{ "node": "AI Agent", "type": "main", "index": 0 }]] },
		"AI Agent": { "main": [[{ "node": "Render PDF", "type": "main", "index": 0 }]] },
		"OpenAI Chat Model": {
			"ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
		},
		"Render PDF": { "main": [[{ "node": "EveryPage", "type": "main", "index": 0 }]] },
		"EveryPage": { "main": [[{ "node": "Post link to Slack", "type": "main", "index": 0 }]] }
	},
	"active": false,
	"settings": { "executionOrder": "v1" },
	"pinData": {}
}

Self-hosted note: community-node tools need N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true before AI Agent nodes may call them.

Built for the people who build n8n.

Four workflows the node walks into on day one.

Self-hosted ops

Events into your warehouse, no middleman

Events: Get Many pages the feed with a since cursor into Postgres or BigQuery - document engagement joins your own analytics stack, with no third-party cloud in the middle.

AI-agent builders

Reports that prove they were read

The agent publishes its report as a tracked link instead of an attachment - you get notified on the first read, and the readership report says how far anyone actually got.

Gated leads → CRM

Leads in the CRM while they read

An instant form-fill trigger shapes the captured fields and forwards them to sales - the importable gated-leads-to-crm workflow ships with the node.

Chat agents

An agent that answers "who read it?"

Attach EveryPage operations as tools on a chat agent and it can list your documents and pull readership on demand - the agent-with-everypage-tool workflow shows the pattern.

What each plan unlocks.

The node is free to install and connects on every EveryPage plan. Fields above your tier say so in their description and link the pricing page; if a run trips a server-side gate anyway, the error names the plan - never a bare 403.

In the n8n nodeFreeBasicPro
Read, download, comment, receipt & lifecycle triggers - webhook and poll
Upload, Import, Get/List, Update Settings, QR, Readership, Events & Delete operations
Protection toggles on upload (block right-click, print, copy)
Form Fill / Email Gate Capture trigger
Link Variants & Replace Content operations

n8n questions.

What can I automate with the EveryPage n8n node?

Both directions of a document workflow. Operations turn any binary PDF in a workflow into a tracked share link with QR and embed codes, manage settings and per-recipient link variants, replace content behind live links, and bulk-export reader events for your warehouse. The trigger node starts workflows the moment a document is read, downloaded, commented on, receipted, gated, or burned - over verified webhooks or a polling fallback.

Is EveryPage a DocSend alternative with an n8n integration?

Yes - DocSend has no n8n node at all. If you want DocSend-style document tracking that lives inside your own workflows, EveryPage covers the job with a binary-native node, instant verified triggers, and GDPR-friendly analytics that store no reader IP addresses - at a flat price instead of per-user billing.

Are the triggers instant - and are the webhooks verified?

Instant by default: activating the trigger registers a webhook subscription, and events are pushed the moment they happen. Every delivery is verified - HMAC-SHA256 signature, constant-time comparison, five-minute replay tolerance - and unverified deliveries are rejected with a 401. For instances that cannot receive inbound webhooks, poll mode walks the events feed with per-stream cursors so nothing fires twice and nothing is missed.

How do n8n AI Agent nodes use EveryPage?

The node is marked usableAsTool, so AI Agent nodes can call its operations as typed tools - upload a generated report and return the tracked link, look up who read a document, pull a readership summary. On self-hosted n8n, set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true; the importable workflows on this page and in the package show working agent setups.

Can I install it on n8n Cloud - and which plans do I need?

Self-hosted first: the package is n8n-nodes-everypage and installs from the Community Nodes panel as soon as it is live on npm - publication is underway, alongside n8n's community-node verification for Cloud availability. The API the node wraps is live today on every EveryPage plan (see the developer docs). On the plan side everything connects free; the Form Fill trigger, Link Variants, and Replace Content are Pro because the product features they drive are Pro.

Do tracked links have to expire?

No - expiry is a control you set per document, never a default. Give a link an expiry date, a view budget, or neither; on Pro, links can be set to never expire, so the link your workflow published keeps working for as long as you want it to. Workflows can also manage expiry themselves through Update Settings.

Put a tracked link at the end of every workflow.

Install the node, paste an API key, and the next document your workflow produces will tell you whether anyone read it - page by page, over a webhook you can verify.

Compare plans

Related: all integrations · the Make integration · raw webhooks · the DocSend alternative · sharing PDFs without storing IPs