All Guides
API

🔔 Webhooks Guide

Advanced patterns for building robust webhook integrations.

18 min read Developer Guide

Webhook Security

Signature Verification

Every webhook includes an HMAC-SHA256 signature. Always verify this before processing:

Node.js
const 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');
});

IP Allowlisting

For additional security, allowlist our webhook IPs:

Vigthoria Webhook IPs:
- 52.89.214.238
- 34.212.75.30
- 54.218.53.128
Always Verify

Never trust a webhook without signature verification. Attackers can spoof webhook requests to your endpoint.

Event Types Reference

Generation Events

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 Events (GoA)

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.

Account Events

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%).

Idempotency & Deduplication

Webhooks may be delivered more than once. Use the event ID for deduplication:

Node.js with Redis
const 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;
  }
}
Python with PostgreSQL
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

Retry Behavior

Vigthoria retries failed webhook deliveries with exponential backoff:

A delivery is considered failed if:

Best Practice

Return 200 immediately, then process asynchronously. This prevents timeouts and ensures delivery success.

Async Processing Pattern

Node.js with Bull Queue
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
  }
});

Testing Webhooks

Local Development with ngrok

# 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

Dashboard Test Events

  1. Go to Dashboard → API Settings → Webhooks
  2. Click â‹® menu on your webhook
  3. Select "Send Test Event"
  4. Choose event type and send

Webhook Log Viewer

The dashboard shows recent webhook deliveries with:

Webhook Checklist