Skip to content
Skillv1.0.0

webflow-observability

Set up observability for Webflow integrations — Prometheus metrics for API calls, OpenTelemetry tracing, structured logging with pino, Grafana dashboards, and alerting for rate limits, errors, and lat

by jeremylongshore(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jeremylongshore/tons-of-skills-marketplace (plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md). Install upstream with npx skills add jeremylongshore/tons-of-skills-marketplace --skill webflow-observability. Copyright stays with the author (MIT).

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

// 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

// 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

// 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

// 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

// 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

# 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

{
  "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

Next Steps

For incident response, see webflow-incident-runbook.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jeremylongshore-tons-of-skills-marketplace-webflow-obser-9ac27b/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jeremylongshore-tons-of-skills-marketplace-webflow-obser-9ac27b.ocm.jsonjson
{
  "ocm": "1",
  "id": "jeremylongshore-tons-of-skills-marketplace-webflow-obser-9ac27b",
  "kind": "skill",
  "name": "webflow-observability",
  "description": "Set up observability for Webflow integrations — Prometheus metrics for API calls, OpenTelemetry tracing, structured logging with pino, Grafana dashboards, and alerting for rate limits, errors, and latency. Trigger with phrases like \"webflow monitoring\", \"webflow metrics\", \"webflow observability\", \"monitor webflow\", \"webflow alerts\", \"webflow tracing\".",
  "publisher": "jeremylongshore",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "saas",
      "design",
      "no-code",
      "webflow",
      "skills-sh"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Set up observability for Webflow integrations — Prometheus metrics for API calls, OpenTelemetry tracing, structured logging with pino, Grafana dashboards, and alerting for rate limits, errors, and latency. Trigger with phrases like \"webflow monitoring\", \"webflow metrics\", \"webflow observability\", \"monitor webflow\", \"webflow alerts\", \"webflow tracing\"."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/jeremylongshore/tons-of-skills-marketplace",
      "path": "plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/jeremylongshore/tons-of-skills-marketplace/blob/HEAD/plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md",
      "key": "jeremylongshore/tons-of-skills-marketplace/plugins/saas-packs/webflow-pack/skills/webflow-observability/SKILL.md"
    },
    "compatibility": "Designed for Claude Code",
    "allowed_tools": [
      "Read,",
      "Write,",
      "Edit"
    ],
    "license": "MIT"
  },
  "instructions": "# Webflow Observability\n\n## Overview\n\nFull observability stack for Webflow Data API v2 integrations: Prometheus metrics\nfor API call counting and latency, OpenTelemetry distributed tracing, structured\nJSON logging, and alerting rules for error rate and rate limit exhaustion.\n\n## Prerequisites\n\n- `prom-client` for Prometheus metrics\n- `@opentelemetry/api` for tracing (optional)\n- `pino` for structured logging\n- Prometheus + Grafana (or compatible backend)\n\n## Instructions\n\n### Step 1: Prometheus Metrics\n\n```typescript\n// src/observability/metrics.ts\nimport { Registry, Counter, Histogram, Gauge ",
  "cost": {
    "context_tokens": 2425
  }
}

Fetch it by URL: GET /api/v1/registry/jeremylongshore-tons-of-skills-marketplace-webflow-obser-9ac27b/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.