webflow-webhooks-events
Implement Webflow webhook registration, signature verification, and event handling for form_submission, site_publish, ecomm_new_order, page_created, and more. Use when setting up webhook endpoints, implementing event-driven workflows, or handling Webflow notifications. Trigger with phrases like "webflow webhook", "webflow events", "webflow webhook signature", "handle webflow events", "webflow notifications".
What this skill does
# Webflow Webhooks & Events
## Overview
Register, verify, and handle Webflow Data API v2 webhooks. Covers all trigger types,
HMAC signature verification, idempotent processing, and event routing patterns.
## Prerequisites
- Webflow API token with `sites:write` scope (for registering webhooks)
- HTTPS endpoint accessible from the internet
- `crypto` module (Node.js built-in)
- Redis or database for idempotency (optional)
## Webhook API Reference
| Operation | Method | Endpoint |
|-----------|--------|----------|
| List webhooks | GET | `/v2/sites/{site_id}/webhooks` |
| Create webhook | POST | `/v2/sites/{site_id}/webhooks` |
| Get webhook | GET | `/v2/webhooks/{webhook_id}` |
| Delete webhook | DELETE | `/v2/webhooks/{webhook_id}` |
**Limits:** Max 75 webhook registrations per `triggerType` per site.
## Supported Trigger Types
| triggerType | Description | Payload |
|-------------|-------------|---------|
| `form_submission` | Form submitted on site | Form data, submitter info |
| `site_publish` | Site published | Site ID, publish domains |
| `page_created` | New page created | Page ID, title, slug |
| `page_metadata_updated` | Page SEO/meta changed | Page ID, updated fields |
| `page_deleted` | Page removed | Page ID |
| `ecomm_new_order` | New ecommerce order | Order details, items, customer |
| `ecomm_order_changed` | Order status updated | Order ID, new status |
| `collection_item_created` | CMS item created | Collection ID, item data |
| `collection_item_changed` | CMS item updated | Collection ID, item data |
| `collection_item_deleted` | CMS item removed | Collection ID, item ID |
| `collection_item_unpublished` | CMS item unpublished | Collection ID, item ID |
## Instructions
### Step 1: Register Webhooks via API
```typescript
import { WebflowClient } from "webflow-api";
const webflow = new WebflowClient({
accessToken: process.env.WEBFLOW_API_TOKEN!,
});
const siteId = process.env.WEBFLOW_SITE_ID!;
const webhookUrl = "https://your-app.com/webhooks/webflow";
async function registerWebhooks() {
const triggerTypes = [
"form_submission",
"site_publish",
"ecomm_new_order",
"collection_item_created",
"collection_item_changed",
];
for (const triggerType of triggerTypes) {
const webhook = await webflow.webhooks.create(siteId, {
triggerType,
url: webhookUrl,
// form_submission supports filtering to a specific form
...(triggerType === "form_submission" && {
filter: { name: "contact-form" }, // Filter by form name
}),
});
console.log(`Registered: ${triggerType} -> ${webhook.id}`);
}
}
// List existing webhooks
async function listWebhooks() {
const { webhooks } = await webflow.webhooks.list(siteId);
for (const wh of webhooks!) {
console.log(`${wh.triggerType}: ${wh.url} (${wh.id})`);
}
}
// Delete a webhook
async function deleteWebhook(webhookId: string) {
await webflow.webhooks.delete(webhookId);
}
```
### Step 2: Webhook Endpoint with Signature Verification
```typescript
import express from "express";
import crypto from "crypto";
const app = express();
// CRITICAL: Use raw body for signature verification
app.post(
"/webhooks/webflow",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["x-webflow-signature"] as string;
const secret = process.env.WEBFLOW_WEBHOOK_SECRET!;
// Verify HMAC-SHA256 signature
if (!verifySignature(req.body, signature, secret)) {
console.error("Webhook signature verification failed");
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
// Respond immediately — process async
res.status(200).json({ received: true });
// Handle event asynchronously
try {
await handleWebflowEvent(event);
} catch (error) {
console.error("Webhook processing error:", error);
}
}
);
function verifySignature(
rawBody: Buffer,
signature: string,
secret: string
): boolean {
if (!signature || !secret) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} catch {
return false; // Length mismatch
}
}
```
### Step 3: Event Router
```typescript
type WebflowTriggerType =
| "form_submission"
| "site_publish"
| "page_created"
| "page_metadata_updated"
| "page_deleted"
| "ecomm_new_order"
| "ecomm_order_changed"
| "collection_item_created"
| "collection_item_changed"
| "collection_item_deleted"
| "collection_item_unpublished";
interface WebflowWebhookEvent {
triggerType: WebflowTriggerType;
payload: Record<string, any>;
site: { id: string; shortName: string };
}
const eventHandlers: Record<WebflowTriggerType, (payload: any) => Promise<void>> = {
form_submission: async (payload) => {
console.log("New form submission:", payload.formData);
// Forward to CRM, send email, etc.
},
site_publish: async (payload) => {
console.log("Site published:", payload.site?.shortName);
// Invalidate cache, notify team, etc.
},
ecomm_new_order: async (payload) => {
console.log("New order:", payload.orderId);
// Create invoice, update inventory, notify fulfillment
},
ecomm_order_changed: async (payload) => {
console.log("Order updated:", payload.orderId, payload.status);
// Update order status in your system
},
page_created: async (payload) => {
console.log("New page:", payload.pageId);
},
page_metadata_updated: async (payload) => {
console.log("Page metadata updated:", payload.pageId);
},
page_deleted: async (payload) => {
console.log("Page deleted:", payload.pageId);
},
collection_item_created: async (payload) => {
console.log("CMS item created:", payload.itemId);
// Sync to external database, index for search, etc.
},
collection_item_changed: async (payload) => {
console.log("CMS item changed:", payload.itemId);
// Update external database
},
collection_item_deleted: async (payload) => {
console.log("CMS item deleted:", payload.itemId);
// Remove from external database
},
collection_item_unpublished: async (payload) => {
console.log("CMS item unpublished:", payload.itemId);
// Remove from public-facing systems
},
};
async function handleWebflowEvent(event: WebflowWebhookEvent): Promise<void> {
const handler = eventHandlers[event.triggerType];
if (!handler) {
console.log(`Unhandled event type: ${event.triggerType}`);
return;
}
await handler(event.payload);
console.log(`Processed: ${event.triggerType}`);
}
```
### Step 4: Idempotent Processing
Prevent duplicate processing with event tracking:
```typescript
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);
async function processOnce(
eventId: string,
handler: () => Promise<void>
): Promise<boolean> {
// SET NX — only succeeds if key doesn't exist
const acquired = await redis.set(
`webflow:event:${eventId}`,
Date.now().toString(),
"EX", 86400 * 7, // 7-day TTL
"NX"
);
if (!acquired) {
console.log(`Event ${eventId} already processed — skipping`);
return false;
}
try {
await handler();
return true;
} catch (error) {
// Remove key on failure so retry can process
await redis.del(`webflow:event:${eventId}`);
throw error;
}
}
// Usage in webhook handler
app.post("/webhooks/webflow", /* middleware */, async (req, res) => {
res.status(200).json({ received: true });
const event = JSON.parse(req.body.toString());
const eventId = `${event.triggerType}-${Date.now()}`;
await processOnce(eventId, () => handleWebflowEvent(event));
});
```
### Step 5: Testing Webhooks Locally
```bash
# Terminal 1: Start your server
npm run dev
# Terminal 2: Expose via ngrok
ngrok http 3000
# CoRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.