Skip to content
Back to skills

Webflow Observability

ASecurity

\"Set up observability for Webflow integrations \\u2014 Prometheus metrics\\ \\ for API calls,\\nOpenTelemetry tracing, structured logging with pino, Grafana dashboards,\\n\\ and alerting for rate limits, errors, and latency.\\nTrigger with phrases like \\\"\\ webflow monitoring\\\", \\\"webflow metrics\\\",\\n\\\"webflow observability\\\", \\\"monitor\\ \\ webflow\\\", \\\"webflow alerts\\\", \\\"webflow tracing\\\".\\n\"

  • 3 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 12, 2026
ai-agentstypescriptgoexpressgitapibackenddevopsdocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add thedixitjain/the-mega-skill-library --skill webflow-observability --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Webflow Observability?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Webflow Observability
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thedixitjain-webflow-observability/badge)](https://www.skillsdirectory.com/skills/thedixitjain-webflow-observability)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: webflow-observability
description: "\"Set up observability for Webflow integrations \\u2014 Prometheus metrics\\ \\ for API calls,\\nOpenTelemetry tracing, structured logging with pino, Grafana dashboards,\\n\\ and alerting for rate limits, errors, and latency.\\nTrigger with phrases like \\\"\\ webflow monitoring\\\", \\\"webflow metrics\\\",\\n\\\"webflow observability\\\", \\\"monitor\\ \\ webflow\\\", \\\"webflow alerts\\\", \\\"webflow tracing\\\".\\n\""
allowed-tools: "Read, Write, Edit"
category: devops-and-infra
source_repo: jeremylongshore/claude-code-plugins-plus-skills
source_path: "plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md"
source_url: https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md
---

# Webflow Observability

## Overview

Full observability stack for Webflow Data API v2 integrations: Prometheus metrics
for API call counting and latency, OpenTelemetry distributed tracing, structured
JSON logging, and alerting rules for error rate and rate limit exhaustion.

## Prerequisites

- `prom-client` for Prometheus metrics
- `@opentelemetry/api` for tracing (optional)
- `pino` for structured logging
- Prometheus + Grafana (or compatible backend)

## Instructions

### Step 1: Prometheus Metrics

```typescript
// src/observability/metrics.ts
import { Registry, Counter, Histogram, Gauge } from "prom-client";

export const registry = new Registry();

// API request counter (by operation and status)
export const apiRequests = new Counter({
  name: "webflow_api_requests_total",
  help: "Total Webflow API requests",
  labelNames: ["operation", "status_code", "method"] as const,
  registers: [registry],
});

// Request duration histogram
export const apiDuration = new Histogram({
  name: "webflow_api_request_duration_seconds",
  help: "Webflow API request duration in seconds",
  labelNames: ["operation"] as const,
  buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10],
  registers: [registry],
});

// Error counter by type
export const apiErrors = new Counter({
  name: "webflow_api_errors_total",
  help: "Webflow API errors by status code",
  labelNames: ["operation", "status_code", "error_type"] as const,
  registers: [registry],
});

// Rate limit remaining gauge
export const rateLimitRemaining = new Gauge({
  name: "webflow_rate_limit_remaining",
  help: "Remaining API calls before rate limit",
  registers: [registry],
});

// CMS items gauge (track total items across collections)
export const cmsItemCount = new Gauge({
  name: "webflow_cms_items_total",
  help: "Total CMS items by collection",
  labelNames: ["collection", "site"] as const,
  registers: [registry],
});

// Webhook event counter
export const webhookEvents = new Counter({
  name: "webflow_webhook_events_total",
  help: "Received webhook events by trigger type",
  labelNames: ["trigger_type", "status"] as const,
  registers: [registry],
});
```

### Step 2: Instrumented Client Wrapper

```typescript
// src/observability/instrumented-client.ts
import { WebflowClient } from "webflow-api";
import { apiRequests, apiDuration, apiErrors, rateLimitRemaining } from "./metrics.js";

export async function instrumentedCall<T>(
  operation: string,
  method: string,
  fn: () => Promise<T>
): Promise<T> {
  const timer = apiDuration.startTimer({ operation });

  try {
    const result = await fn();

    apiRequests.inc({ operation, status_code: "200", method });
    timer();
    return result;
  } catch (error: any) {
    const statusCode = String(error.statusCode || error.status || "unknown");

    apiRequests.inc({ operation, status_code: statusCode, method });
    apiErrors.inc({
      operation,
      status_code: statusCode,
      error_type: statusCode === "429" ? "rate_limit" : statusCode >= "500" ? "server" : "client",
    });

    timer();
    throw error;
  }
}

// Usage
const { sites } = await instrumentedCall("sites.list", "GET", () =>
  webflow.sites.list()
);

const { items } = await instrumentedCall("items.listLive", "GET", () =>
  webflow.collections.items.listItemsLive(collectionId)
);

const item = await instrumentedCall("items.create", "POST", () =>
  webflow.collections.items.createItem(collectionId, {
    fieldData: { name: "Test", slug: "test" },
  })
);
```

### Step 3: Metrics Endpoint

```typescript
// api/metrics.ts
import express from "express";
import { registry } from "../observability/metrics.js";

const app = express();

app.get("/metrics", async (req, res) => {
  res.set("Content-Type", registry.contentType);
  res.send(await registry.metrics());
});
```

### Step 4: OpenTelemetry Distributed Tracing

```typescript
// src/observability/tracing.ts
import { trace, SpanStatusCode, context } from "@opentelemetry/api";

const tracer = trace.getTracer("webflow-integration", "1.0.0");

export async function tracedCall<T>(
  operationName: string,
  attributes: Record<string, string>,
  fn: () => Promise<T>
): Promise<T> {
  return tracer.startActiveSpan(`webflow.${operationName}`, async (span) => {
    span.setAttributes({
      "webflow.operation": operationName,
      ...attributes,
    });

    try {
      const result = await fn();
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error: any) {
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error.message,
      });
      span.recordException(error);
      span.setAttributes({
        "webflow.error.status_code": String(error.statusCode || "unknown"),
      });
      throw error;
    } finally {
      span.end();
    }
  });
}

// Usage
const { collections } = await tracedCall(
  "collections.list",
  { "webflow.site_id": siteId },
  () => webflow.collections.list(siteId)
);
```

### Step 5: Structured Logging

```typescript
// src/observability/logger.ts
import pino from "pino";

export const logger = pino({
  name: "webflow-integration",
  level: process.env.LOG_LEVEL || "info",
  serializers: {
    err: pino.stdSerializers.err,
  },
  // Redact sensitive fields
  redact: {
    paths: ["accessToken", "apiToken", "*.authorization", "req.headers.authorization"],
    censor: "[REDACTED]",
  },
});

// Log API calls with consistent structure
export function logApiCall(
  operation: string,
  durationMs: number,
  status: "success" | "error",
  metadata?: Record<string, any>
) {
  const logFn = status === "error" ? logger.error.bind(logger) : logger.info.bind(logger);

  logFn({
    service: "webflow",
    operation,
    durationMs,
    status,
    ...metadata,
  }, `webflow.${operation} ${status} (${durationMs}ms)`);
}

// Log webhook events
export function logWebhook(triggerType: string, status: "processed" | "failed" | "skipped") {
  logger.info({
    service: "webflow",
    event: "webhook",
    triggerType,
    status,
  }, `webhook.${triggerType} ${status}`);
}
```

### Step 6: AlertManager Rules

```yaml
# prometheus/webflow-alerts.yml
groups:
  - name: webflow
    rules:
      - alert: WebflowHighErrorRate
        expr: |
          (
            rate(webflow_api_errors_total[5m]) /
            rate(webflow_api_requests_total[5m])
          ) > 0.05
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Webflow API error rate > 5%"
          description: "{{ $value | humanizePercentage }} errors in last 5m"

      - alert: WebflowRateLimited
        expr: |
          rate(webflow_api_errors_total{status_code="429"}[5m]) > 0
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Webflow API rate limited"

      - alert: WebflowHighLatency
        expr: |
          histogram_quantile(0.95,
            rate(webflow_api_request_duration_seconds_bucket[5m])
          ) > 3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Webflow P95 latency > 3s"

      - alert: WebflowDown
        expr: |
          sum(rate(webflow_api_requests_total{status_code=~"5.."}[5m])) /
          sum(rate(webflow_api_requests_total[5m])) > 0.5
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Webflow API > 50% server errors"

      - alert: WebflowRateLimitLow
        expr: webflow_rate_limit_remaining < 10
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Webflow rate limit nearly exhausted"
```

### Step 7: Grafana Dashboard Queries

```json
{
  "panels": [
    {
      "title": "Request Rate by Operation",
      "targets": [{ "expr": "sum by (operation) (rate(webflow_api_requests_total[5m]))" }]
    },
    {
      "title": "Error Rate",
      "targets": [{ "expr": "sum(rate(webflow_api_errors_total[5m])) / sum(rate(webflow_api_requests_total[5m]))" }]
    },
    {
      "title": "Latency P50 / P95 / P99",
      "targets": [
        { "expr": "histogram_quantile(0.5, rate(webflow_api_request_duration_seconds_bucket[5m]))", "legendFormat": "p50" },
        { "expr": "histogram_quantile(0.95, rate(webflow_api_request_duration_seconds_bucket[5m]))", "legendFormat": "p95" },
        { "expr": "histogram_quantile(0.99, rate(webflow_api_request_duration_seconds_bucket[5m]))", "legendFormat": "p99" }
      ]
    },
    {
      "title": "Rate Limit Remaining",
      "targets": [{ "expr": "webflow_rate_limit_remaining" }]
    },
    {
      "title": "Webhook Events by Type",
      "targets": [{ "expr": "sum by (trigger_type) (rate(webflow_webhook_events_total[5m]))" }]
    }
  ]
}
```

## Output

- Prometheus metrics: request count, latency histogram, error rate, rate limit gauge
- OpenTelemetry tracing for end-to-end request visibility
- Structured JSON logging with PII redaction
- AlertManager rules for error rate, latency, and rate limits
- Grafana dashboard panels

## Error Handling

| Issue | Cause | Solution |
|-------|-------|----------|
| Missing metrics | Calls not instrumented | Wrap with `instrumentedCall()` |
| High cardinality | Too many label values | Limit `operation` to known set |
| Trace gaps | Missing context propagation | Pass OTel context in async calls |
| Alert storms | Thresholds too sensitive | Increase `for` duration |

## Resources

- [Prometheus Best Practices](https://prometheus.io/docs/practices/naming/)
- [OpenTelemetry JS](https://opentelemetry.io/docs/languages/js/)
- [pino Documentation](https://github.com/pinojs/pino)

## Next Steps

For incident response, see `webflow-incident-runbook`.

---

**Source:** [`jeremylongshore/claude-code-plugins-plus-skills`](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) → `plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.