n8n Integration Guide
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
- n8n instance (self-hosted via Docker, or n8n Cloud)
- Surmado API key (
sur_live_...) — create at app.surmado.com → Settings → API Keys - 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
- Webhook trigger node. n8n gives you a URL like
https://n8n.yourdomain.com/webhook/abc123. - Configure Surmado to POST report completions to that URL via
webhook_urlwhen creating a report. - IF node to filter on
eventandreport.product. - (Optional) HTTP Request node to call
GET /v1/reports/{{ $json.report.id }}for deeper data. - 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 asummaryobject — onlyreport.tokenandreport.pdf_url.
Workflow 1: AI Visibility to Google Sheets
Node 1 — Webhook trigger
- HTTP method: POST
- Path:
surmado-signal(n8n will generatehttps://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:
- 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