{
  "name": "FilingPulse — real-time insider trade alerts (webhook)",
  "nodes": [
    {
      "parameters": {
        "content": "## FilingPulse → Slack/Discord (real-time)\n\nFilingPulse POSTs each matching SEC filing to this workflow seconds after it lands on EDGAR — no polling.\n\n**Setup (5 minutes):**\n1. Activate this workflow, then copy the **Production URL** from the Webhook node.\n2. Get a free API key at https://filingpulse.io/signup.html\n3. Create the subscription (fill in your key + the URL from step 1):\n```\ncurl -X POST https://api.filingpulse.io/v1/webhooks \\\n  -H \"X-API-Key: fp_your_key\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"YOUR_PRODUCTION_URL\", \"events\": [\"form4\"], \"filters\": {\"tickers\": [\"AAPL\", \"KMI\"]}}'\n```\n4. The response includes a `secret` (shown once) — paste it into the **Verify signature** node.\n5. Paste your Slack incoming-webhook URL into the **Send alert** node. For Discord, change the body to `{\"content\": ...}` and use your Discord webhook URL.\n\nRemove `filters` to receive every Form 4. Docs: https://filingpulse.io/docs.html#webhooks\n\n*Self-hosted n8n only: if the Code node errors on `require('crypto')`, set the env var `NODE_FUNCTION_ALLOW_BUILTIN=crypto` and restart n8n.*",
        "height": 620,
        "width": 460
      },
      "id": "sticky-setup-wh",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-260, 120]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "filingpulse-alerts",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "id": "webhook-in",
      "name": "FilingPulse webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [260, 300],
      "webhookId": "f11a6e55-7a1e-4c3a-9d1e-filingpulse1"
    },
    {
      "parameters": {
        "jsCode": "// Paste the subscription secret returned by POST /v1/webhooks (it is shown exactly once).\nconst SECRET = 'PASTE_YOUR_WEBHOOK_SECRET';\n\n// Every FilingPulse delivery is signed: X-FilingPulse-Signature: sha256=HMAC_SHA256(secret, raw_body).\n// ALWAYS verify before trusting a payload — anyone can POST to a public URL.\nconst crypto = require('crypto');\nconst item = items[0];\nconst raw = Buffer.from(item.binary.data.data, 'base64');\nconst sig = String((item.json.headers || {})['x-filingpulse-signature'] || '');\nconst expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(raw).digest('hex');\nconst a = Buffer.from(sig);\nconst b = Buffer.from(expected);\nif (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {\n  throw new Error('Bad X-FilingPulse-Signature — delivery dropped. Check the SECRET in this node.');\n}\nreturn [{ json: JSON.parse(raw.toString('utf8')) }];"
      },
      "id": "verify-sig",
      "name": "Verify signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [480, 300]
    },
    {
      "parameters": {
        "jsCode": "// One factual line per filing — descriptive only.\nconst ev = $json.event;\nconst d = $json.data || {};\nconst issuer = (d.issuer && d.issuer.name) || '?';\nconst ticker = d.issuer && d.issuer.ticker ? ' (' + d.issuer.ticker + ')' : '';\nlet line;\nif (ev === 'filing.form4') {\n  const owners = (d.reporting_owners || []).map(o => o.name).join(', ');\n  const codes = [...new Set((d.transactions || []).map(t => t.transaction_code).filter(Boolean))].join(',');\n  line = 'Form ' + (d.form_type || '4') + ': ' + owners + ' @ ' + issuer + ticker +\n         ' — codes: ' + (codes || 'holdings only');\n} else if (ev === 'filing.8k') {\n  const items8k = (d.items || []).map(i => i.code).join(', ');\n  line = '8-K: ' + issuer + ticker + ' — items ' + items8k;\n} else if (ev === 'filing.registration') {\n  line = (d.form_type || 'Registration') + ' [' + (d.stage || '?') + ']: ' + issuer +\n         ' — file ' + (d.file_number || '?');\n} else {\n  line = ev + ': ' + issuer;\n}\nline += '\\nfiled ' + (d.filed_date || '?') + ' — accession ' + (d.accession || '?');\nreturn [{ json: { text: line } }];"
      },
      "id": "format-alert",
      "name": "Format alert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [700, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/REPLACE/WITH/YOURS",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: $json.text }) }}",
        "options": {}
      },
      "id": "send-alert",
      "name": "Send alert",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [920, 300]
    }
  ],
  "connections": {
    "FilingPulse webhook": {
      "main": [[{ "node": "Verify signature", "type": "main", "index": 0 }]]
    },
    "Verify signature": {
      "main": [[{ "node": "Format alert", "type": "main", "index": 0 }]]
    },
    "Format alert": {
      "main": [[{ "node": "Send alert", "type": "main", "index": 0 }]]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}
