Skip to main content

n8n Integration Guide

Paste this into Claude Code, Copilot, Cursor, or any AI coding agent to apply changes in your repo.

Quick answer: Use n8n’s Webhook trigger node to receive Surmado report.completed events. Point a Surmado webhook_url at the n8n webhook URL, filter on event and report.product, and pipe the payload into Google Sheets, Slack, Postgres, or any other integration. n8n self-hosted is free; n8n Cloud is $20/month.

Reading time: 16 minutes


Prerequisites

  1. n8n instance (self-hosted via Docker, or n8n Cloud)
  2. Surmado API key (sur_live_...) — create at app.surmado.com → Settings → API Keys
  3. A public URL for your n8n webhook (n8n Cloud handles this; self-hosted needs a domain or tunnel)

Why n8n

  • Self-hostable: your workflow runs on your own infrastructure (Docker, Kubernetes, bare metal)
  • Open source: inspect every node, extend with custom code
  • No per-task billing: unlimited executions on self-hosted (only server cost)
  • 400+ built-in nodes: Notion, Google Sheets, Slack, Airtable, PostgreSQL, HTTP Request, Code, etc.
  • Credentials vault: store your Surmado API key once, reference it across workflows

Quick Start: Self-Hosted via Docker

docker volume create n8n_data

docker run -d --name n8n \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  -e N8N_HOST="n8n.yourdomain.com" \
  -e WEBHOOK_URL="https://n8n.yourdomain.com/" \
  n8nio/n8n

Then reverse-proxy n8n.yourdomain.com to http://localhost:5678 with Caddy / Nginx / Traefik so Surmado can reach your webhook endpoint over HTTPS.


Store Your Surmado API Key

In n8n, go to Credentials → New → Header Auth (or use the generic HTTP Request node’s built-in credential):

  • Name: Surmado API
  • Header name: Authorization
  • Header value: Bearer sur_live_YOUR_API_KEY

Reference this credential from any HTTP Request node that calls api.surmado.com.


The Integration Pattern

  1. Webhook trigger node. n8n gives you a URL like https://n8n.yourdomain.com/webhook/abc123.
  2. Configure Surmado to POST report completions to that URL via webhook_url when creating a report.
  3. IF node to filter on event and report.product.
  4. (Optional) HTTP Request node to call GET /v1/reports/{{ $json.report.id }} for deeper data.
  5. Destination nodes: Google Sheets, Slack, Notion, Postgres, etc.

Example payload from the Webhook node

{
  "event": "report.completed",
  "timestamp": "2026-04-15T12:00:00+00:00",
  "report": {
    "id": "rpt_abc123xyz",
    "token": "SIG-2026-04-A1B2C",
    "product": "signal",
    "status": "completed",
    "tier": "basic",
    "data_url": "https://api.surmado.com/v1/reports/rpt_abc123xyz",
    "pdf_url": "https://api.surmado.com/v1/reports/view/VIEW_TOKEN",
    "summary": {
      "business_name": "Acme Coffee Roasters",
      "presence_score": 34.2,
      "authority_score": 42.7,
      "competitive_rank": 4,
      "top_competitor": "Blue Bottle"
    }
  }
}

Strategy reports ("product": "solutions") arrive without a summary object — only report.token and report.pdf_url.


Workflow 1: AI Visibility to Google Sheets

Node 1 — Webhook trigger

  • HTTP method: POST
  • Path: surmado-signal (n8n will generate https://n8n.yourdomain.com/webhook/surmado-signal)
  • Response mode: Immediately (return 200 OK as soon as n8n receives)

Pass that URL to Surmado:

curl -X POST https://api.surmado.com/v1/reports/signal \
  -H "Authorization: Bearer sur_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brand_slug": "acme_corp",
    "url": "https://acme.com",
    "email": "founder@acme.com",
    "industry": "B2B SaaS",
    "location": "United States",
    "persona": "Small business owners",
    "pain_points": "Losing track of customer conversations",
    "brand_details": "Simple CRM with email integration",
    "direct_competitors": "HubSpot, Pipedrive",
    "webhook_url": "https://n8n.yourdomain.com/webhook/surmado-signal"
  }'

Click Listen for test event in the Webhook node, then trigger a report so n8n captures the payload shape.

Node 2 — IF

Condition 1: {{ $json.event }} equals report.completed Condition 2: {{ $json.report.product }} equals signal

Combine: AND

Node 3 — Google Sheets: Append row

Operation: Append Spreadsheet / Sheet: your tracking sheet

Column mapping:

  • Date: {{ $json.timestamp }}
  • Token: {{ $json.report.token }}
  • Business: {{ $json.report.summary.business_name }}
  • Presence Score: {{ $json.report.summary.presence_score }}
  • Authority Score: {{ $json.report.summary.authority_score }}
  • Competitive Rank: {{ $json.report.summary.competitive_rank }}
  • Top Competitor: {{ $json.report.summary.top_competitor }}

Activate the workflow. Every completed AI Visibility report is now tracked.


Workflow 2: Site Audit with Conditional Slack Alert

Webhook → IF (report.product = scan) → Google Sheets (always) → IF (report.summary.performance_score < 70) → Slack.

Slack message:

:warning: Performance regression on {{ $json.report.summary.business_name }}

Performance: {{ $json.report.summary.performance_score }} (below 70 threshold)
SEO: {{ $json.report.summary.seo_score }}
Accessibility: {{ $json.report.summary.accessibility_score }}
Critical issues: {{ $json.report.summary.critical_issues_count }}

View report: {{ $json.report.pdf_url }}

Workflow 3: Strategy PDF Digest

Strategy doesn’t include a summary. Pattern:

  1. Webhook → IF (report.product = solutions) → Send Email (Gmail, SMTP, SendGrid).

Email body:

<p>Strategy report <code>{{ $json.report.token }}</code> is ready.</p>
<p><a href="{{ $json.report.pdf_url }}">View the full report (PDF)</a></p>

Fetching Deeper Data via REST API

The webhook summary covers most needs. For platform variance, per-page issues, or content gap details, add an HTTP Request node after the filter:

Node — HTTP Request

  • Method: GET
  • URL: https://api.surmado.com/v1/reports/{{ $json.report.id }}
  • Authentication: predefined credential → Surmado API (the Header Auth you created above)
  • Response format: JSON

Downstream nodes can then reference {{ $json.public_intelligence.data.platform_variance.chatgpt.presence_score }}, {{ $json.public_intelligence.data.audit_data.enrichment.content_gaps.executive_summary }}, etc.


Webhook Signature Verification

For production, verify the X-Surmado-Signature header before trusting the payload. Surmado signs webhooks with HMAC-SHA256 over a canonicalized JSON representation.

Function node — Verify signature

const crypto = require('crypto');

const secret = $env.SURMADO_WEBHOOK_SECRET;
const signature = $('Webhook').first().json.headers['x-surmado-signature'];
const body = $('Webhook').first().json.body;

function canonicalize(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return '[' + value.map(canonicalize).join(',') + ']';
  const keys = Object.keys(value).sort();
  return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalize(value[k])).join(',') + '}';
}

function asciiEscape(str) {
  return str.replace(/[\u0080-\uffff]/g, c =>
    '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4)
  );
}

const canonical = asciiEscape(canonicalize(body));
const expected = crypto.createHmac('sha256', secret).update(canonical).digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'))) {
  throw new Error('Invalid webhook signature');
}

return [{ json: body }];

Chain: Webhook → Function (verify) → IF (filter) → destination nodes.


Token Formats

  • AI Visibility: SIG-YYYY-MM-XXXXX (regex: SIG-\d{4}-\d{2}-[A-Z0-9]{5})
  • Site Audit: SCN-YYYY-MM-XXXXX (regex: SCN-\d{4}-\d{2}-[A-Z0-9]{5})
  • Strategy: SOL-YYYY-MM-XXXXX (regex: SOL-\d{4}-\d{2}-[A-Z0-9]{5})

Tokens don’t expire. REST API signed download URLs are valid for ~15 minutes; pdf_url magic links in webhooks are valid for ~30 days.


Advanced Patterns

Route by product with Switch

Use a Switch node on $json.report.product with cases for signal, scan, solutions. Each case branches to a product-specific workflow. One webhook URL handles every report type.

Persist to Postgres for time-series analysis

Add a Postgres node after the IF filter that INSERTs (timestamp, token, report_id, product, business_name, presence_score, authority_score, ...) into a reports table. Query later with Grafana or Metabase for trend dashboards.

Multi-client agency routing

Use a Data store (or external database) mapping business_name / brand_slug to client configuration (which Slack channel, which sheet tab, etc.). Look up per report and route dynamically.


Troubleshooting

Webhook doesn’t fire — Activate the workflow (n8n only processes webhooks when active, not during testing). Confirm webhook_url was set when creating the report. Use webhook.site as a staging URL to verify Surmado sends the expected payload.

HTTP Request node returns 401 — Credential not attached, or key missing Bearer prefix. The full header value must be Bearer sur_live_....

Fields are empty downstream — Confirm the product type. Strategy webhooks don’t include report.summary; only report.token and report.pdf_url are populated.

Self-hosted webhook unreachable — Surmado requires HTTPS. If running n8n on localhost, use a tunnel (ngrok, Cloudflare Tunnel) or deploy behind a reverse proxy with a valid TLS cert.

Signature verification fails on non-ASCII — The canonicalize helper above handles it; if you wrote your own, ensure you ASCII-escape non-ASCII characters to match Python’s json.dumps(..., ensure_ascii=True) output used by Surmado.


Related: API Integration Guide | Structured Data & Intelligence Tokens | Zapier Integration | Make Integration | Notion Integration