Webhook Implementation Guide
Set up and manage webhooks for real-time AI event notifications and automation.
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
- Navigate to Settings > Webhooks
- Click "Add Webhook"
- Enter your endpoint URL
- Select events to subscribe to
- 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
- Always verify signatures - Prevent spoofed requests
- Respond quickly - Return 200 within 30 seconds
- Use queues - Process events asynchronously
- Handle idempotency - Expect duplicate deliveries
- Log everything - Debug issues effectively
- Monitor delivery - Track success rates
- Set up alerts - Know when webhooks fail
- 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