Tips > Data, APIs & Webhooks

Monitor Webhook Health by Logging All Incoming Requests

In production, you need visibility into what is hitting your webhooks -- request volume, error rates, and payload patterns.

To monitor webhook health in production, log every incoming request to a database or Google Sheet. Add a Code node right after the Webhook node that records the timestamp, method, source IP, event type, and payload size, then branch into a logging path and the main processing path. This gives you an audit trail and traffic visibility.

Why log every incoming webhook request?

In production, you need visibility into what is hitting your webhooks -- request volume, error rates, and payload patterns. Add a logging branch early in your workflow that writes every incoming request to a Google Sheet or database table. This gives you an audit trail and helps debug issues.

Real-world example: Log every incoming webhook request to a Postgres table for monitoring and debugging, without slowing down the main processing pipeline.

How do you log webhook requests?

Add a Code node immediately after the Webhook node that prepares the log entry, then split into two branches: one for logging, one for processing.

// Code node: "Prepare Log Entry"
const input = $input.first().json;

const logEntry = {
  received_at: new Date().toISOString(),
  method: input.method || 'POST',
  path: input.path || 'unknown',
  source_ip: input.headers['x-forwarded-for']
    || input.headers['x-real-ip']
    || 'unknown',
  user_agent: input.headers['user-agent'] || 'unknown',
  content_type: input.headers['content-type'] || 'unknown',
  payload_size: JSON.stringify(input.body || {}).length,
  idempotency_key: input.headers['x-idempotency-key'] || null,
  event_type: input.body?.type || input.body?.event || 'unknown',
  status: 'received'
};

// Pass both the log entry and original data downstream
return [{
  json: {
    log: logEntry,
    originalData: input
  }
}];

Logging branch (Postgres node):

-- Use parameterized queries to prevent SQL injection
INSERT INTO webhook_log (
  received_at, method, path, source_ip,
  user_agent, content_type, payload_size,
  idempotency_key, event_type, status
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
-- Query Parameters:
-- $1: {{ $json.log.received_at }}
-- $2: {{ $json.log.method }}
-- $3: {{ $json.log.path }}
-- $4: {{ $json.log.source_ip }}
-- $5: {{ $json.log.user_agent }}
-- $6: {{ $json.log.content_type }}
-- $7: {{ $json.log.payload_size }}
-- $8: {{ $json.log.idempotency_key || null }}
-- $9: {{ $json.log.event_type }}
-- $10: {{ $json.log.status }}

For a Google Sheets alternative (simpler setup):

{
  "operation": "append",
  "sheetId": "your-sheet-id",
  "range": "WebhookLog!A:J",
  "values": [
    "={{ $json.log.received_at }}",
    "={{ $json.log.method }}",
    "={{ $json.log.path }}",
    "={{ $json.log.source_ip }}",
    "={{ $json.log.event_type }}",
    "={{ $json.log.payload_size }}",
    "={{ $json.log.status }}"
  ]
}

How do you spot anomalies over time?

Then build a scheduled monitoring workflow:

-- Run daily: check for anomalies
SELECT
  DATE(received_at) as day,
  COUNT(*) as total_requests,
  COUNT(*) FILTER (WHERE status = 'failed') as failed,
  ROUND(
    COUNT(*) FILTER (WHERE status = 'failed')::numeric / COUNT(*)::numeric * 100, 2
  ) as error_rate_pct
FROM webhook_log
WHERE received_at > NOW() - INTERVAL '7 days'
GROUP BY DATE(received_at)
ORDER BY day DESC;

This logging adds minimal overhead (one async database insert) but gives you full visibility into webhook traffic patterns and errors over time.

Related: Set a Unique Encryption Key and Back It Up · Use the HTTP Request Node as a Universal Connector

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call