Product

Webhooks & Events

Receive real-time notifications when events occur in your Twig AI organization

TL;DR

Receive real-time notifications when events occur in your Twig AI organization. Webhooks are HTTP callbacks that notify your application when specific events happen. Instead of polling for changes, Twig AI pushes updates to your server.

Key Takeaways

  • What are Webhooks?
  • Supported Events
  • Setting Up Webhooks
  • Webhook Payload
  • Receiving Webhooks
  • Security

Receive real-time notifications when events occur in your Twig AI organization.

What are Webhooks?

Webhooks are HTTP callbacks that notify your application when specific events happen. Instead of polling for changes, Twig AI pushes updates to your server.

Benefits:

  • Real-time notifications
  • No polling required
  • Efficient resource usage
  • Event-driven architecture

Supported Events

Interaction Events

EventDescriptionPayload
interaction.createdNew question askedInteraction object
interaction.completedResponse generatedInteraction + response
interaction.feedbackUser provided feedbackInteraction + feedback

Agent Events

EventDescriptionPayload
agent.createdNew agent createdAgent object
agent.updatedAgent configuration changedAgent object + changes
agent.deletedAgent removedAgent ID

Data Source Events

EventDescriptionPayload
datasource.createdNew data source addedDataSource object
datasource.processedProcessing completedDataSource + stats
datasource.failedProcessing failedDataSource + error

User Events

EventDescriptionPayload
user.createdNew user addedUser object
user.updatedUser details changedUser object + changes

Setting Up Webhooks

Via UI

  1. Navigate to SettingsWebhooks
  2. Click Create Webhook
  3. Configure:
    • URL: Your endpoint (must be HTTPS)
    • Events: Select events to receive
    • Secret: Auto-generated signing secret
  4. Click Create
  5. Test webhook
  6. Save

Via API

curl -X POST https://api.twig.so/api/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/twig",
    "events": [
      "interaction.completed",
      "agent.updated"
    ],
    "description": "Production webhook"
  }'

Response:

{
  "id": "wh_abc123",
  "url": "https://your-server.com/webhooks/twig",
  "events": ["interaction.completed", "agent.updated"],
  "secret": "whsec_xyz789...",
  "status": "active",
  "createdAt": "2024-01-15T10:00:00Z"
}

Webhook Payload

Standard Format

All webhooks follow this structure:

{
  "id": "evt_abc123",
  "type": "interaction.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    // Event-specific data
  },
  "organizationId": "org-123"
}

Event Examples

Interaction Completed:

{
  "id": "evt_001",
  "type": "interaction.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "interaction": {
      "id": "int-456",
      "prompt": "What is your refund policy?",
      "response": "Our refund policy allows...",
      "agentId": "agent-123",
      "userId": "user-789",
      "sources": [...]
    }
  },
  "organizationId": "org-123"
}

Data Source Processed:

{
  "id": "evt_002",
  "type": "datasource.processed",
  "timestamp": "2024-01-15T11:00:00Z",
  "data": {
    "dataSource": {
      "id": "ds-101",
      "name": "Product Docs",
      "type": "WEBSITE",
      "status": "COMPLETED",
      "stats": {
        "documentsProcessed": 245,
        "tokensProcessed": 125000,
        "duration": 180
      }
    }
  },
  "organizationId": "org-123"
}

Receiving Webhooks

Endpoint Implementation

Node.js/Express:

const express = require('express');
const crypto = require('crypto');
const app = express();

app.post('/webhooks/twig', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-twig-signature'];
  const payload = req.body;
  
  // Verify signature
  if (!verifySignature(payload, signature)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(payload);
  
  // Handle event
  switch(event.type) {
    case 'interaction.completed':
      handleInteraction(event.data);
      break;
    case 'agent.updated':
      handleAgentUpdate(event.data);
      break;
    // Add more handlers
  }
  
  res.status(200).send('OK');
});

function verifySignature(payload, signature) {
  const secret = process.env.WEBHOOK_SECRET;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Python/Flask:

from flask import Flask, request
import hmac
import hashlib

app = Flask(__name__)

@app.route('/webhooks/twig', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Twig-Signature')
    payload = request.get_data()
    
    # Verify signature
    if not verify_signature(payload, signature):
        return 'Invalid signature', 401
    
    event = request.get_json()
    
    # Handle event
    if event['type'] == 'interaction.completed':
        handle_interaction(event['data'])
    elif event['type'] == 'agent.updated':
        handle_agent_update(event['data'])
    
    return 'OK', 200

def verify_signature(payload, signature):
    secret = os.environ['WEBHOOK_SECRET'].encode()
    expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

Security

Signature Verification

Always verify webhook signatures:

import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
    
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

HTTPS Required

  • All webhook URLs must use HTTPS
  • Valid SSL certificate required
  • Self-signed certificates not accepted

IP Whitelisting

Webhook requests come from:

34.123.45.67/32
52.89.123.45/32

Add to firewall whitelist if needed.

Best Practices

1. Respond Quickly

✅ Return 200 OK immediately ✅ Process asynchronously ✅ Use job queue for heavy processing

app.post('/webhooks/twig', async (req, res) => {
  // Verify signature
  if (!verifySignature(req.body, req.headers['x-twig-signature'])) {
    return res.status(401).send('Invalid signature');
  }
  
  // Return 200 immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  const event = JSON.parse(req.body);
  await queue.add('process-webhook', event);
});

2. Handle Retries

✅ Implement idempotency ✅ Store event IDs to detect duplicates ✅ Handle same event multiple times safely

const processedEvents = new Set();

function handleWebhook(event) {
  // Check if already processed
  if (processedEvents.has(event.id)) {
    return; // Skip duplicate
  }
  
  // Process event
  processEvent(event);
  
  // Mark as processed
  processedEvents.add(event.id);
}

3. Error Handling

try {
  await processWebhook(event);
} catch (error) {
  console.error('Webhook processing error:', error);
  // Log but don't fail
  // Twig will retry
}

4. Logging

✅ Log all webhook receipts ✅ Track processing time ✅ Monitor failure rates

logger.info('Webhook received', {
  eventId: event.id,
  type: event.type,
  timestamp: event.timestamp
});

Retry Logic

Automatic Retries

Twig AI automatically retries failed webhooks:

AttemptDelayTotal Time
1Immediate0s
25 seconds5s
330 seconds35s
45 minutes5m 35s
51 hour1h 5m 35s

After 5 attempts, webhook is marked as failed.

Handling Failed Webhooks

View failed webhooks:

  1. Settings → Webhooks
  2. Select webhook
  3. View "Failed Deliveries"
  4. Retry manually if needed

Testing Webhooks

Test Mode

curl -X POST https://api.twig.so/api/webhooks/wh_abc123/test \
  -H "Authorization: Bearer YOUR_API_KEY"

Sends test event to your endpoint.

Local Development

Use tools like ngrok for local testing:

# Start ngrok
ngrok http 3000

# Update webhook URL to ngrok URL
https://abc123.ngrok.io/webhooks/twig

Event Simulator

# Simulate interaction.completed event
curl -X POST https://your-server.com/webhooks/twig \
  -H "Content-Type: application/json" \
  -H "X-Twig-Signature: test_signature" \
  -d '{
    "id": "evt_test",
    "type": "interaction.completed",
    "timestamp": "2024-01-15T10:00:00Z",
    "data": {...}
  }'

Monitoring

Webhook Metrics

Track these metrics:

  • Delivery success rate
  • Average response time
  • Retry rate
  • Error types

Dashboard

Webhook: Production
├─ Status: Active
├─ Success Rate: 99.2%
├─ Avg Response: 145ms
├─ Failed (24h): 3
└─ Last Delivery: 2 minutes ago

Alerts

Configure alerts for:

  • Failed deliveries > 10
  • Response time > 5s
  • Success rate < 95%

Troubleshooting

Webhook Not Received

Check:

  1. Webhook is active
  2. URL is correct and accessible
  3. HTTPS with valid certificate
  4. Events are subscribed
  5. Firewall allows Twig IPs

Signature Verification Fails

Check:

  1. Using correct secret
  2. Payload not modified
  3. Using raw body (not parsed JSON)
  4. Comparing hex digests correctly

High Failure Rate

Check:

  1. Server is responding within 10s
  2. Returning 200 OK status
  3. Not throwing errors
  4. Handling retries correctly

Next Steps


Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the ask query parameter:

GET /dev/product/developer-api/webhooks.md?ask=<question>

The question should be specific, self-contained, and written in natural language. The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.

People also ask

Related Pages

Last updated January 25, 2026