diff --git a/deploy/n8n/README.md b/deploy/n8n/README.md index 40810df..2f66e2e 100644 --- a/deploy/n8n/README.md +++ b/deploy/n8n/README.md @@ -12,4 +12,5 @@ Tested on n8n 2.47.6 (`n8nio/n8n:2.47.6`): - With a Header Auth credential and an OpenAI-compatible stub in place of a real model: no secret or a wrong secret gets 403; an empty body, a non-string `url`, `http://`, `localhost`, an IP literal, `*.internal` and a URL with user info get 400; an unresolvable host, a page that returns 404 and an unreachable model get 502; `https://example.com` gets 200 with `{ url, summary }`. - Through a gateway on a local chain, bad input (backend 400) and a backend error (502) settle nothing, and the valid call settles 0.02 USDC. - `Validate input` was changed to `caseSensitive: false` after these runs. For n8n 2.47.6, review of the `n8n-workflow` 2.47.2 source showed that this lowercases the URL for both checks without changing the regex patterns. The change has not been tested at runtime. +- After these runs, the sticky notes were rewritten to the n8n sticky note guidelines and the placeholder node and condition ids were replaced with random UUIDs, as submitted to the n8n template library. Node parameters and connections did not change. - Still untested: summary quality, the chat model node with a real provider, and the n8n Creator Hub submission checks. diff --git a/deploy/n8n/summarize-url.json b/deploy/n8n/summarize-url.json index b64f86a..b4b20b3 100644 --- a/deploy/n8n/summarize-url.json +++ b/deploy/n8n/summarize-url.json @@ -3,12 +3,12 @@ "nodes": [ { "parameters": { - "content": "## Paid web page summarizer\n\nSell summaries of public web pages through an Agent Commerce Gateway. The gateway prices requests and handles x402 payments; n8n fetches and summarizes pages without handling payments or wallet keys.\n\n### How it works\n1. The gateway returns `402` until the buyer supplies payment authorization for `summarize_url`.\n2. After verifying that authorization, it calls this webhook with `{ \"url\": \"https://...\" }` and the `X-Gateway-Secret` header.\n3. The workflow checks the URL format, fetches the page and asks your model for a summary.\n4. With the default `authorization` payment flow, the gateway settles after a `200` response. Bad input (`400`) and fetch or model failures (`502`) settle nothing.\n\n### Setup\n1. Create a Header Auth credential whose header name is `X-Gateway-Secret` and whose value is a long random secret. Select it in `Receive paid request`.\n2. Attach your chat model credential to `Chat model`, or replace it with another chat model node.\n3. Set the page text limit and summary length in `Set service options`.\n4. Activate the workflow and copy its Production URL.\n5. Deploy a gateway that calls that URL with the same secret: https://acg.devlab.group/docs/deploy\n\nRequirements: n8n 2.x (tested on 2.47.6), a chat model credential and an Agent Commerce Gateway.", - "height": 540, + "content": "### How it works\nThis workflow sells short summaries of public web pages to AI agents through an Agent Commerce Gateway. The gateway prices each request and handles x402 payments, so n8n never handles payments or wallet keys.\n\n1. The gateway returns `402` until the buyer pays for the `summarize_url` resource.\n2. After verifying the payment authorization, it calls this webhook with `{ \"url\": \"https://...\" }` and the `X-Gateway-Secret` header.\n3. The workflow checks the URL, fetches the page and asks your chat model for a summary.\n4. With the gateway's default `authorization` flow, payment settles only after a `200` response. Invalid input (`400`) and fetch or model failures (`502`) are not charged.\n\nEvery paid request runs the workflow once. Summaries are not cached, so two payments for the same URL produce two runs.\n\n### Setup\n1. Create a Header Auth credential with the header name `X-Gateway-Secret` and a long random value. Select it in `Receive paid request`.\n2. Attach your chat model credential to `Chat model`, or replace that node with another chat model.\n3. Activate the workflow and copy its Production URL.\n4. Deploy a gateway that calls that URL with the same secret: https://acg.devlab.group/docs/deploy\n\nTested on n8n 2.47.6.\n\n### Customization\nChange the page text limit and summary length in `Set service options`. To sell a different service, replace the fetch and summarize steps and keep the `400` and `502` responses for failures.", + "height": 780, "width": 620, "color": 1 }, - "id": "a0000000-0000-4000-8000-000000000001", + "id": "f7c95b91-d6f7-4902-b437-468af50728d9", "name": "Overview", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -16,12 +16,12 @@ }, { "parameters": { - "content": "### 1. Request and settings\nThe gateway posts the buyer's JSON body here. Header Auth rejects requests with a missing or incorrect secret. Set the page text limit and summary length in `Set service options`.", + "content": "## Receive the paid request\nOnly the gateway can call this webhook: Header Auth rejects a missing or wrong secret. Editable limits are in `Set service options`.", "height": 120, "width": 440, "color": 7 }, - "id": "a0000000-0000-4000-8000-000000000002", + "id": "6a052f51-66fa-4c1e-8456-b99824304fb4", "name": "Note: request", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -29,12 +29,12 @@ }, { "parameters": { - "content": "### 2. Check the input\nThe URL check requires HTTPS and a dotted domain name, and blocks some local suffixes. Invalid input returns 400 before fetching. DNS results are not checked; network isolation is still required.", - "height": 100, + "content": "## Check the URL\nAccepts only HTTPS URLs with a domain name and blocks local suffixes. Anything else gets a `400` before any fetch.", + "height": 120, "width": 440, "color": 7 }, - "id": "a0000000-0000-4000-8000-000000000003", + "id": "faeaf64b-92d8-4552-94e1-5ce3707b808e", "name": "Note: check", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -42,12 +42,12 @@ }, { "parameters": { - "content": "### 3. Fetch and summarize\n`Fetch page` disables redirects and uses a 10-second timeout. After downloading the response, the `Summarize page` prompt expression strips markup, truncates the text to the configured limit and sends it to your model.", + "content": "## Fetch and summarize\nThe page is fetched without redirects (10-second timeout), stripped of markup and truncated before it reaches the model.", "height": 110, "width": 540, "color": 7 }, - "id": "a0000000-0000-4000-8000-000000000004", + "id": "b61baa29-1b7c-414f-814d-031c78e9a6cb", "name": "Note: summarize", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -55,12 +55,12 @@ }, { "parameters": { - "content": "### 4. Respond\n`Return summary` returns 200 with the summary. `Reject bad input` returns 400; `Report service error` returns 502. Under the default `authorization` flow, the gateway settles after a 2xx response and leaves failures unpaid.", + "content": "## Respond to the gateway\n`200` returns the summary. `400` and `502` tell the gateway the call failed, so the buyer is not charged under the default flow.", "height": 120, "width": 440, "color": 7 }, - "id": "a0000000-0000-4000-8000-000000000005", + "id": "7bacd8e1-72cf-40de-ab3f-e0186bc3d182", "name": "Note: respond", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -68,12 +68,12 @@ }, { "parameters": { - "content": "### Webhook security\nKeep Header Auth enabled. Share the production URL and secret only with your gateway. Accepted domain names can resolve to private addresses; run n8n where it cannot reach private networks.", - "height": 130, + "content": "## Keep the webhook private\nKeep Header Auth on and share the URL and secret only with your gateway, or anyone can get summaries without paying. Domain names can resolve to private addresses; run n8n where it cannot reach private networks.", + "height": 170, "width": 440, "color": 3 }, - "id": "a0000000-0000-4000-8000-000000000006", + "id": "e940f6dd-0209-4288-9651-9e935e6260d9", "name": "Note: webhook security", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -88,7 +88,7 @@ "options": {} }, "webhookId": "e1d5c1a0-6b0e-4a54-9c63-4d6a0c7f2b11", - "id": "b0000000-0000-4000-8000-000000000001", + "id": "a6d9b547-6c43-42ab-bb8a-cd996d4379f0", "name": "Receive paid request", "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, @@ -99,19 +99,19 @@ "assignments": { "assignments": [ { - "id": "c0000000-0000-4000-8000-000000000001", + "id": "5fd6f734-7f3a-4441-9f09-425e038cf3f3", "name": "url", "value": "={{ $json.body.url }}", "type": "string" }, { - "id": "c0000000-0000-4000-8000-000000000002", + "id": "3c43094d-4d44-4ef1-80d1-b858aef6fd34", "name": "maxChars", "value": 12000, "type": "number" }, { - "id": "c0000000-0000-4000-8000-000000000003", + "id": "83cb2269-6398-4c71-a294-c343d3c90ed0", "name": "summaryLength", "value": "3 to 5 sentences", "type": "string" @@ -120,7 +120,7 @@ }, "options": {} }, - "id": "b0000000-0000-4000-8000-000000000002", + "id": "6e932726-2fb2-441d-a22e-3161751e9675", "name": "Set service options", "type": "n8n-nodes-base.set", "typeVersion": 3.4, @@ -137,7 +137,7 @@ }, "conditions": [ { - "id": "d0000000-0000-4000-8000-000000000001", + "id": "8de33e77-135d-4f02-9e3a-684b3542e241", "leftValue": "={{ $json.url }}", "rightValue": "^https://([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}(:[0-9]{1,5})?(/[^\\s]*)?$", "operator": { @@ -146,7 +146,7 @@ } }, { - "id": "d0000000-0000-4000-8000-000000000002", + "id": "8d85fee9-048e-4c43-8687-5145ec5873ec", "leftValue": "={{ $json.url }}", "rightValue": "\\.(local|localhost|internal|lan|home\\.arpa)(:[0-9]+)?(/|$)", "operator": { @@ -159,7 +159,7 @@ }, "options": {} }, - "id": "b0000000-0000-4000-8000-000000000003", + "id": "219835c1-f6e9-47aa-97f3-9330bbf8b0d3", "name": "Validate input", "type": "n8n-nodes-base.if", "typeVersion": 2.2, @@ -183,7 +183,7 @@ } } }, - "id": "b0000000-0000-4000-8000-000000000004", + "id": "40805bad-15fa-4e9c-b4cc-bc83aa9e0fa8", "name": "Fetch page", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, @@ -202,7 +202,7 @@ ] } }, - "id": "b0000000-0000-4000-8000-000000000005", + "id": "78f7ad6a-0c52-441a-8aa6-e7a5a7eeb766", "name": "Summarize page", "type": "@n8n/n8n-nodes-langchain.chainLlm", "typeVersion": 1.9, @@ -218,7 +218,7 @@ }, "options": {} }, - "id": "b0000000-0000-4000-8000-000000000006", + "id": "8513c1af-be64-452f-a3e4-f7306594250f", "name": "Chat model", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "typeVersion": 1.3, @@ -232,7 +232,7 @@ "responseCode": 200 } }, - "id": "b0000000-0000-4000-8000-000000000007", + "id": "7f7eeddb-2fa4-499e-b4a9-bef29b7af63c", "name": "Return summary", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, @@ -246,7 +246,7 @@ "responseCode": 400 } }, - "id": "b0000000-0000-4000-8000-000000000008", + "id": "48151627-8939-437e-a3f2-8a23b6e020f3", "name": "Reject bad input", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, @@ -260,7 +260,7 @@ "responseCode": 502 } }, - "id": "b0000000-0000-4000-8000-000000000009", + "id": "015a36df-f311-4fec-be60-c057016a7d0a", "name": "Report service error", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4,