Advanced patterns for building robust webhook integrations.
Every webhook includes an HMAC-SHA256 signature. Always verify this before processing:
Node.jsconst crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEquals(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your endpoint
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-vigthoria-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(payload);
// Process event...
res.status(200).send('OK');
});
For additional security, allowlist our webhook IPs:
Vigthoria Webhook IPs: - 52.89.214.238 - 34.212.75.30 - 54.218.53.128
Never trust a webhook without signature verification. Attackers can spoof webhook requests to your endpoint.
generation.started
Fired when an AI generation begins processing.
generation.completed
Fired when generation finishes successfully. Includes result URL.
generation.failed
Fired when generation encounters an error. Includes error details.
workflow.started
Autonomous workflow execution has begun.
workflow.step_completed
Individual step in workflow finished. Includes step details.
workflow.finished
Entire workflow completed. Includes final results.
workflow.failed
Workflow encountered unrecoverable error.
subscription.created
New subscription started.
subscription.updated
Subscription plan changed (upgrade/downgrade).
subscription.cancelled
Subscription cancelled (may still be active until period end).
usage.threshold
API usage reached configured threshold (50%, 80%, 100%).
Webhooks may be delivered more than once. Use the event ID for deduplication:
Node.js with Redisconst redis = require('redis');
const client = redis.createClient();
async function processWebhook(event) {
const eventKey = `webhook:${event.id}`;
// Check if already processed
const processed = await client.get(eventKey);
if (processed) {
console.log(`Event ${event.id} already processed, skipping`);
return { status: 'duplicate' };
}
// Mark as processing (with short TTL in case of crash)
await client.set(eventKey, 'processing', 'EX', 60);
try {
// Process the event
await handleEvent(event);
// Mark as completed (keep for 24 hours)
await client.set(eventKey, 'completed', 'EX', 86400);
return { status: 'success' };
} catch (error) {
// Remove key so retry can process
await client.del(eventKey);
throw error;
}
}
from sqlalchemy import Column, String, DateTime
from datetime import datetime, timedelta
class ProcessedWebhook(Base):
__tablename__ = 'processed_webhooks'
event_id = Column(String, primary_key=True)
processed_at = Column(DateTime, default=datetime.utcnow)
def process_webhook(event, db_session):
# Check if already processed
existing = db_session.query(ProcessedWebhook).filter_by(
event_id=event['id']
).first()
if existing:
return {'status': 'duplicate'}
# Record before processing
record = ProcessedWebhook(event_id=event['id'])
db_session.add(record)
db_session.commit()
try:
handle_event(event)
return {'status': 'success'}
except Exception as e:
db_session.delete(record)
db_session.commit()
raise
Vigthoria retries failed webhook deliveries with exponential backoff:
A delivery is considered failed if:
Return 200 immediately, then process asynchronously. This prevents timeouts and ensures delivery success.
const Queue = require('bull');
const webhookQueue = new Queue('webhooks');
// Endpoint - respond immediately
app.post('/webhook', (req, res) => {
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Add to queue for async processing
webhookQueue.add(req.body);
// Respond immediately
res.status(200).send('OK');
});
// Process queue in background
webhookQueue.process(async (job) => {
const event = job.data;
switch (event.type) {
case 'generation.completed':
await handleGenerationComplete(event.data);
break;
case 'workflow.finished':
await handleWorkflowFinished(event.data);
break;
// ... other handlers
}
});
# Install ngrok npm install -g ngrok # Start your local server node server.js # Running on port 3000 # Expose to internet ngrok http 3000 # Use the ngrok URL in Vigthoria dashboard # https://abc123.ngrok.io/webhook
The dashboard shows recent webhook deliveries with: