Overview
Webflow Webhooks enable real-time event notifications for form submissions, CMS changes, ecommerce orders, site publishing, and more. Webhooks deliver HTTP POST requests to your endpoint whenever specific events occur in Webflow.
Webhooks created via API or OAuth include signature verification. Dashboard webhooks do not include signatures.
Download Skill
Add this skill to your AI agent for webhook integration assistance:
https://skills.224ai.au/webflow-webhooks.skill
Quick Start
Create webhook
Register a webhook via the Webflow API for your desired event type: POST https://api.webflow.com/v2/sites/{site_id}/webhooks
Set up endpoint
Create an endpoint that accepts POST requests with raw body parsing: app . post ( '/webhooks/webflow' , express . raw ({ type: 'application/json' }), handler );
Verify signatures
Validate x-webflow-signature and x-webflow-timestamp headers using HMAC-SHA256
Process events
Route events by triggerType and handle each accordingly
Acknowledge receipt
Return 200 status to confirm receipt (other statuses trigger retries)
Event Types
Webflow supports 14 webhook event types across 6 categories:
Category Events Required Scope Forms form_submissionforms:readSite site_publishsites:readPages page_created, page_metadata_updated, page_deletedpages:readEcommerce ecomm_new_order, ecomm_order_changed, ecomm_inventory_changedecommerce:readCMS collection_item_created, collection_item_changed, collection_item_deleted, collection_item_unpublished, collection_item_publishedcms:readComments comment_createdcomments:read
See references/event-types.md for complete payload schemas and examples for all 14 event types.
Signature Verification
Webhooks created via API or OAuth include cryptographic signatures for verification.
Verification Code
const crypto = require ( 'crypto' );
function verifyWebflowSignature ( rawBody , signature , timestamp , secret ) {
// Check timestamp to prevent replay attacks (5 minute window)
const currentTime = Date . now ();
if ( Math . abs ( currentTime - parseInt ( timestamp )) > 300000 ) {
return false ;
}
// Generate HMAC signature
const signedContent = ` ${ timestamp } : ${ rawBody } ` ;
const expectedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( signedContent )
. digest ( 'hex' );
// Timing-safe comparison
try {
return crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expectedSignature )
);
} catch {
return false ; // Different lengths = invalid
}
}
Always use raw body for verification. Never verify against parsed JSON. Configure your framework accordingly.
Framework-Specific Setup
const express = require ( 'express' );
const app = express ();
// Use express.raw() for webhook endpoint
app . post ( '/webhooks/webflow' ,
express . raw ({ type: 'application/json' }),
( req , res ) => {
const signature = req . headers [ 'x-webflow-signature' ];
const timestamp = req . headers [ 'x-webflow-timestamp' ];
if ( ! signature || ! timestamp ) {
return res . status ( 400 ). send ( 'Missing headers' );
}
const isValid = verifyWebflowSignature (
req . body . toString (),
signature ,
timestamp ,
process . env . WEBFLOW_WEBHOOK_SECRET
);
if ( ! isValid ) {
return res . status ( 400 ). send ( 'Invalid signature' );
}
const event = JSON . parse ( req . body );
// Process event...
res . status ( 200 ). send ( 'OK' );
}
);
// pages/api/webhooks/webflow.ts
import { NextApiRequest , NextApiResponse } from 'next' ;
import crypto from 'crypto' ;
export const config = {
api: {
bodyParser: false , // Disable body parsing
},
};
async function getRawBody ( req : NextApiRequest ) : Promise < string > {
const chunks = [];
for await ( const chunk of req ) {
chunks . push ( typeof chunk === 'string' ? Buffer . from ( chunk ) : chunk );
}
return Buffer . concat ( chunks ). toString ( 'utf8' );
}
export default async function handler (
req : NextApiRequest ,
res : NextApiResponse
) {
if ( req . method !== 'POST' ) {
return res . status ( 405 ). json ({ error: 'Method not allowed' });
}
const signature = req . headers [ 'x-webflow-signature' ] as string ;
const timestamp = req . headers [ 'x-webflow-timestamp' ] as string ;
if ( ! signature || ! timestamp ) {
return res . status ( 400 ). json ({ error: 'Missing headers' });
}
const rawBody = await getRawBody ( req );
const isValid = verifyWebflowSignature (
rawBody ,
signature ,
timestamp ,
process . env . WEBFLOW_WEBHOOK_SECRET !
);
if ( ! isValid ) {
return res . status ( 400 ). json ({ error: 'Invalid signature' });
}
const event = JSON . parse ( rawBody );
// Process event...
res . status ( 200 ). json ({ success: true });
}
const fastify = require ( 'fastify' )();
fastify . post ( '/webhooks/webflow' , {
config: {
rawBody: true
}
}, async ( request , reply ) => {
const signature = request . headers [ 'x-webflow-signature' ];
const timestamp = request . headers [ 'x-webflow-timestamp' ];
if ( ! signature || ! timestamp ) {
return reply . code ( 400 ). send ( 'Missing headers' );
}
const isValid = verifyWebflowSignature (
request . rawBody ,
signature ,
timestamp ,
process . env . WEBFLOW_WEBHOOK_SECRET
);
if ( ! isValid ) {
return reply . code ( 400 ). send ( 'Invalid signature' );
}
const event = JSON . parse ( request . rawBody );
// Process event...
return reply . code ( 200 ). send ( 'OK' );
});
Processing Events
Route events by triggerType and handle each event appropriately:
app . post ( '/webhooks/webflow' , express . raw ({ type: 'application/json' }), ( req , res ) => {
// Verify signature (see above)
const event = JSON . parse ( req . body );
switch ( event . triggerType ) {
case 'form_submission' :
console . log ( 'New form submission:' , event . payload . data );
// Send to CRM, email notification, etc.
break ;
case 'ecomm_new_order' :
console . log ( 'New order:' , event . payload . orderId );
// Process fulfillment, send confirmation email
break ;
case 'collection_item_created' :
console . log ( 'New CMS item:' , event . payload );
// Sync to external database, trigger build
break ;
case 'collection_item_published' :
console . log ( 'Published CMS items:' , event . payload . items );
// Trigger static site regeneration
break ;
case 'site_publish' :
console . log ( 'Site published:' , event . site );
// Clear CDN cache, notify team
break ;
default :
console . log ( 'Unhandled event:' , event . triggerType );
}
res . status ( 200 ). send ( 'OK' );
});
Environment Variables
Never hard-code secrets. Always use environment variables or a secrets manager.
# For webhooks created via OAuth App
WEBFLOW_WEBHOOK_SECRET = your_oauth_client_secret
# For webhooks created via API (after April 2025)
WEBFLOW_WEBHOOK_SECRET = whsec_xxxxx # Returned when creating webhook
Webhook Management API
Create, list, get, and delete webhooks programmatically:
Create a Webhook
const webhook = {
triggerType: 'form_submission' ,
url: 'https://your-domain.com/webhooks/webflow'
};
const response = await fetch (
'https://api.webflow.com/v2/sites/YOUR_SITE_ID/webhooks' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . WEBFLOW_TOKEN } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ( webhook )
}
);
const data = await response . json ();
console . log ( 'Webhook created:' , data );
List All Webhooks
const response = await fetch (
'https://api.webflow.com/v2/sites/YOUR_SITE_ID/webhooks' ,
{
headers: {
'Authorization' : `Bearer ${ process . env . WEBFLOW_TOKEN } ` ,
'Accept' : 'application/json'
}
}
);
const data = await response . json ();
data . webhooks . forEach ( webhook => {
console . log ( ` ${ webhook . triggerType } → ${ webhook . url } ` );
});
Delete a Webhook
const response = await fetch (
'https://api.webflow.com/v2/webhooks/WEBHOOK_ID' ,
{
method: 'DELETE' ,
headers: {
'Authorization' : `Bearer ${ process . env . WEBFLOW_TOKEN } `
}
}
);
if ( response . ok ) {
console . log ( 'Webhook deleted successfully' );
}
Best Practices
Always Verify Signatures Use HMAC-SHA256 verification for webhooks created via OAuth or API
Use Raw Body Never verify against parsed JSON — configure framework for raw body parsing
Validate Timestamps Enforce 5-minute window (300000ms) to prevent replay attacks
Return 200 Quickly Acknowledge receipt immediately; process events asynchronously
Handle Retries Webflow retries up to 3 times on failure — implement idempotency
Use HTTPS Webhook endpoints must use HTTPS in production for security
Retry Behavior
Webflow automatically retries failed webhook deliveries:
Retry count : Up to 3 attempts
Retry interval : 10 minutes between attempts
Success status : Only 200 status codes are considered successful
Timeout : 30 seconds per request
Implement idempotency in your webhook handler to safely process duplicate events from retries.
const processedEvents = new Set ();
app . post ( '/webhooks/webflow' , express . raw ({ type: 'application/json' }), ( req , res ) => {
const event = JSON . parse ( req . body );
// Check if already processed (simple idempotency)
const eventId = ` ${ event . triggerType } - ${ event . _id } ` ;
if ( processedEvents . has ( eventId )) {
return res . status ( 200 ). send ( 'Already processed' );
}
// Process event
processedEvents . add ( eventId );
// ... handle event ...
res . status ( 200 ). send ( 'OK' );
});
Debugging
Signature Verification Fails
Common issues:
Using parsed JSON instead of raw body
Wrong secret (OAuth client secret vs webhook secret)
Timestamp out of 5-minute window
Missing or malformed headers
// Debug logging
console . log ( 'Raw body:' , req . body . toString ());
console . log ( 'Signature:' , req . headers [ 'x-webflow-signature' ]);
console . log ( 'Timestamp:' , req . headers [ 'x-webflow-timestamp' ]);
console . log ( 'Secret:' , process . env . WEBFLOW_WEBHOOK_SECRET ?. substring ( 0 , 10 ) + '...' );
Checklist:
Webhook URL is publicly accessible
HTTPS is enabled (required in production)
Firewall allows incoming requests
Endpoint returns 200 status
Correct event type registered
Required API scopes granted
Check event payload schema in references/event-types.md
Some events have nested data structures
Published items are arrays, not single objects
Form submissions store data in payload.data
Important Notes
Webhooks created through the Webflow dashboard do NOT include signature headers
Only webhooks created via OAuth apps or API include x-webflow-signature and x-webflow-timestamp
Timestamp validation (5 minute window) is critical to prevent replay attacks
Return 200 status to acknowledge receipt; other statuses trigger retries
Use HTTPS for production webhook endpoints
Reference Documentation
The skill includes comprehensive reference documentation:
event-types.md — Complete reference for all 14 event types with scopes, payload schemas, and examples
webhook-api.md — REST API v2 endpoints for creating, listing, getting, and deleting webhooks
overview.md — Webhook concepts, delivery behavior, limits, and security considerations
setup.md — Dashboard and API configuration, OAuth, scopes, environment setup
verification.md — HMAC-SHA256 signature verification, common gotchas, debugging
faq.md — FAQ and troubleshooting for delivery issues, signature failures, and API errors
Searching References
# List all references with metadata
python scripts/search_references.py --list
# Search by tag (exact match)
python scripts/search_references.py --tag verification
# Search by keyword
python scripts/search_references.py --search form_submission
License
MIT License - See the repository for details.