Intermediate 11 min read

Webhook Implementation Guide

Set up and manage webhooks for real-time AI event notifications and automation.

Webhook Implementation Guide

Webhook Implementation Guide

Learn how to set up and manage webhooks for real-time notifications and event-driven automation.

What Are Webhooks?

Webhooks are HTTP callbacks that notify your application when events occur in Promptha. Instead of polling for updates, webhooks push data to you in real-time.

Benefits

  • Real-time updates - No polling delay
  • Efficient - Only receive data when events occur
  • Scalable - Handle high volumes of events
  • Automation - Trigger workflows automatically

Common Use Cases

  • Notify users when AI generation completes
  • Sync results to external databases
  • Trigger downstream workflows
  • Monitor credit usage and alerts
  • Log all AI activity

Setting Up Webhooks

Via Dashboard

  1. Navigate to Settings > Webhooks
  2. Click "Add Webhook"
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Save and test

Via API

// Create a webhook subscription
const webhook = await fetch('https://api.promptha.com/v1/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://yourapp.com/webhooks/promptha',
    events: [
      'fabric.completed',
      'fabric.failed',
      'ask.message',
      'credits.low',
      'credits.depleted'
    ],
    secret: 'your_webhook_secret'
  })
});

console.log('Webhook created:', webhook.id);

Available Events

Fabric Events

Event Description Payload
fabric.started Execution began executionId, fabricId, inputs
fabric.completed Execution finished executionId, result, usage
fabric.failed Execution errored executionId, error

Ask Events

Event Description Payload
ask.session.created New session started sessionId, askId
ask.message New message in session sessionId, message
ask.session.ended Session completed sessionId, summary

Credit Events

Event Description Payload
credits.low Below threshold remaining, threshold
credits.depleted Credits exhausted lastUsage
credits.purchased Credits added amount, newBalance

Building a Webhook Handler

Basic Handler (Express)

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

const app = express();
app.use(express.json());

// Middleware to verify webhook signature
function verifySignature(req, res, next) {
  const signature = req.headers['x-promptha-signature'];
  const timestamp = req.headers['x-promptha-timestamp'];
  const payload = JSON.stringify(req.body);

  // Verify timestamp is recent (prevent replay attacks)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    return res.status(401).send('Timestamp too old');
  }

  // Verify signature
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  next();
}

// Webhook endpoint
app.post('/webhooks/promptha', verifySignature, async (req, res) => {
  const { event, data, timestamp } = req.body;

  console.log(`Received event: ${event}`, data);

  try {
    await handleEvent(event, data);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook handling failed:', error);
    res.status(500).send('Internal error');
  }
});

async function handleEvent(event, data) {
  switch (event) {
    case 'fabric.completed':
      await handleFabricComplete(data);
      break;
    case 'fabric.failed':
      await handleFabricFailed(data);
      break;
    case 'ask.message':
      await handleAskMessage(data);
      break;
    case 'credits.low':
      await handleLowCredits(data);
      break;
    default:
      console.log('Unhandled event:', event);
  }
}

Event Handlers

async function handleFabricComplete(data) {
  const { executionId, fabricId, result, usage } = data;

  // Store result in database
  await db.executions.update({
    where: { id: executionId },
    data: {
      status: 'completed',
      result: result,
      tokensUsed: usage.totalTokens,
      completedAt: new Date()
    }
  });

  // Notify user (if applicable)
  const execution = await db.executions.findUnique({
    where: { id: executionId },
    include: { user: true }
  });

  if (execution.user.notificationsEnabled) {
    await sendNotification(execution.user.email, {
      subject: 'Your AI generation is ready',
      body: `Your ${fabricId} result is ready to view.`
    });
  }
}

async function handleFabricFailed(data) {
  const { executionId, error } = data;

  // Update database
  await db.executions.update({
    where: { id: executionId },
    data: {
      status: 'failed',
      error: error.message,
      failedAt: new Date()
    }
  });

  // Alert on critical failures
  if (error.type === 'rate_limit' || error.type === 'api_error') {
    await alertOpsTeam('Fabric execution failed', data);
  }
}

async function handleLowCredits(data) {
  const { remaining, threshold, userId } = data;

  // Send warning email
  const user = await db.users.findUnique({ where: { id: userId } });

  await sendEmail(user.email, {
    subject: 'Low Credits Warning',
    template: 'low-credits',
    data: { remaining, threshold }
  });

  // Log for analytics
  await analytics.track('credits_low', {
    userId,
    remaining,
    threshold
  });
}

Webhook Security

Signature Verification

Always verify webhook signatures:

function verifyWebhookSignature(payload, signature, timestamp, secret) {
  // Create expected signature
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

IP Whitelisting

For additional security, whitelist Promptha IPs:

const PROMPTHA_IPS = [
  '52.xxx.xxx.xxx',
  '52.xxx.xxx.xxx'
  // Get current IPs from docs
];

function isAllowedIP(req) {
  const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  return PROMPTHA_IPS.includes(clientIP);
}

HTTPS Only

Always use HTTPS for webhook endpoints. Promptha will not send webhooks to HTTP URLs in production.

Handling Failures

Retry Policy

Promptha retries failed webhook deliveries:

Attempt Delay Total Time
1 Immediate 0s
2 5 seconds 5s
3 30 seconds 35s
4 5 minutes ~5.5m
5 30 minutes ~35m
6 2 hours ~2.5h

After 6 failed attempts, the webhook is marked as failed and logged.

Idempotency

Handle duplicate deliveries gracefully:

async function handleEvent(event, data) {
  const eventId = data.eventId;

  // Check if already processed
  const existing = await db.webhookEvents.findUnique({
    where: { eventId }
  });

  if (existing) {
    console.log('Duplicate event, skipping:', eventId);
    return;
  }

  // Process and mark as handled
  await db.webhookEvents.create({
    data: {
      eventId,
      event,
      processedAt: new Date()
    }
  });

  // Handle the event
  await processEvent(event, data);
}

Error Responses

Return appropriate status codes:

Code Meaning Action
200 Success Event processed
202 Accepted Queued for processing
400 Bad request Won't retry
401 Unauthorized Won't retry
500 Server error Will retry
503 Unavailable Will retry

Testing Webhooks

Local Development

Use ngrok for local testing:

# Start ngrok tunnel
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/promptha

Test Events

Send test events from the dashboard or API:

// Send a test event
await fetch('https://api.promptha.com/v1/webhooks/test', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    webhookId: 'wh_xxx',
    event: 'fabric.completed',
    data: {
      executionId: 'test_123',
      result: { output: 'Test result' }
    }
  })
});

Webhook Logs

View delivery logs in the dashboard:

  • Delivery attempts and responses
  • Payload contents
  • Timing information
  • Error details

Advanced Patterns

Event Queuing

Queue webhooks for reliable processing:

const Queue = require('bull');
const webhookQueue = new Queue('webhooks');

// Webhook endpoint adds to queue
app.post('/webhooks/promptha', verifySignature, async (req, res) => {
  await webhookQueue.add(req.body);
  res.status(202).send('Accepted');
});

// Process queue in background
webhookQueue.process(async (job) => {
  const { event, data } = job.data;
  await handleEvent(event, data);
});

Event Filtering

Filter events server-side:

// Only subscribe to specific fabrics
await createWebhook({
  url: 'https://yourapp.com/webhooks',
  events: ['fabric.completed'],
  filters: {
    fabricId: ['product-description', 'seo-optimizer']
  }
});

Multiple Endpoints

Route different events to different endpoints:

// Webhook for completions
await createWebhook({
  url: 'https://yourapp.com/webhooks/completions',
  events: ['fabric.completed']
});

// Webhook for alerts
await createWebhook({
  url: 'https://yourapp.com/webhooks/alerts',
  events: ['credits.low', 'fabric.failed']
});

Best Practices

  1. Always verify signatures - Prevent spoofed requests
  2. Respond quickly - Return 200 within 30 seconds
  3. Use queues - Process events asynchronously
  4. Handle idempotency - Expect duplicate deliveries
  5. Log everything - Debug issues effectively
  6. Monitor delivery - Track success rates
  7. Set up alerts - Know when webhooks fail
  8. Use HTTPS - Secure your endpoints

Resources

Ready to create?

Put what you've learned into practice with Promptha's AI-powered tools.

Get Started Free