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.

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.

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
  }
}];
```text
Logging branch (Postgres node):

```sql
-- 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 }}
```text
For a Google Sheets alternative (simpler setup):

```json
{
  "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 }}"
  ]
}
```text
Then build a scheduled monitoring workflow:

```sql
-- 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;
```text
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](../security-best-practices/01-set-a-unique-encryption-key-and-back-it-up.md) | [Use the HTTP Request Node as a Universal Connector](../integration-patterns/01-use-the-http-request-node-as-a-universal-connector.md)

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

191 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 a 20-minute call