Webhooks
BetaReceive real-time notifications for events in your account—usage alerts, payments, and more.
Overview
Webhooks allow your application to receive HTTP callbacks when specific events occur in your LLM Hub account. Instead of polling the API for changes, you'll receive instant notifications.
Note: Webhooks are currently in beta. Event types and payload formats may change.
Available Events
| Event | Description |
|---|---|
usage.threshold | Usage reaches configured threshold (e.g., 80%) |
credits.low | Credit balance drops below threshold |
credits.depleted | Credit balance reaches zero |
payment.completed | Payment processed and credits added |
payment.failed | Payment attempt failed |
api_key.created | New API key created |
api_key.revoked | API key revoked or deleted |
rate_limit.exceeded | Rate limit exceeded (aggregated, not per-request) |
Creating a Webhook
Register a webhook endpoint to start receiving events:
// Configure your webhook endpoint
// POST https://api.llmhub.dev/v1/webhooks
const webhook = await fetch('https://api.llmhub.dev/v1/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://your-app.com/api/llmhub-webhook',
events: ['usage.threshold', 'payment.completed', 'api_key.created'],
secret: 'your-webhook-secret' // Used to sign payloads
})
});
console.log(await webhook.json());
// {
// "id": "wh_abc123",
// "url": "https://your-app.com/api/llmhub-webhook",
// "events": ["usage.threshold", "payment.completed", "api_key.created"],
// "active": true,
// "created_at": "2024-01-15T10:30:00Z"
// }Verifying Signatures
Important: Always verify the webhook signature to ensure requests are from LLM Hub. We sign all webhook payloads with your secret using HMAC-SHA256.
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const expected = Buffer.from(`sha256=${expectedSignature}`);
const received = Buffer.from(signature);
return crypto.timingSafeEqual(expected, received);
}
// In your webhook handler (Express example)
app.post('/api/llmhub-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-llmhub-signature'] as string;
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);
// Handle the event
switch (event.type) {
case 'usage.threshold':
console.log('Usage threshold reached:', event.data);
break;
case 'payment.completed':
console.log('Payment received:', event.data);
break;
default:
console.log('Unhandled event:', event.type);
}
res.status(200).json({ received: true });
});Next.js Handler Example
Complete webhook handler for Next.js App Router:
// app/api/webhooks/llmhub/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === `sha256=${expected}`;
}
export async function POST(request: NextRequest) {
const payload = await request.text();
const signature = request.headers.get('x-llmhub-signature') || '';
if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(payload);
try {
switch (event.type) {
case 'usage.threshold':
await handleUsageThreshold(event.data);
break;
case 'payment.completed':
await handlePaymentCompleted(event.data);
break;
case 'credits.low':
await handleLowCredits(event.data);
break;
}
return NextResponse.json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
return NextResponse.json({ error: 'Handler failed' }, { status: 500 });
}
}
async function handleUsageThreshold(data: { user_id: string; usage: number }) {
// Send notification to user
console.log(`User ${data.user_id} reached ${data.usage}% usage`);
}
async function handlePaymentCompleted(data: { amount: number; credits: number }) {
// Update database, send receipt
console.log(`Payment: ${data.amount} → ${data.credits} credits`);
}
async function handleLowCredits(data: { credits_remaining: number }) {
// Send low balance alert
console.log(`Low credits: ${data.credits_remaining} remaining`);
}Event Payloads
All webhook events follow this structure:
// Example: usage.threshold event
{
"id": "evt_abc123",
"type": "usage.threshold",
"created": "2024-01-15T10:30:00Z",
"data": {
"user_id": "user_xyz",
"threshold": 80,
"current_usage": 82,
"period": "monthly"
}
}
// Example: payment.completed event
{
"id": "evt_def456",
"type": "payment.completed",
"created": "2024-01-15T11:00:00Z",
"data": {
"payment_id": "pay_123",
"amount": 5000,
"currency": "eur",
"credits_added": 500000,
"user_id": "user_xyz"
}
}
// Example: credits.low event
{
"id": "evt_ghi789",
"type": "credits.low",
"created": "2024-01-15T12:00:00Z",
"data": {
"user_id": "user_xyz",
"credits_remaining": 10000,
"threshold": 50000
}
}Managing Webhooks
List, update, or delete your webhooks:
// List all webhooks
const response = await fetch('https://api.llmhub.dev/v1/webhooks', {
headers: { 'Authorization': 'Bearer your-api-key' }
});
const webhooks = await response.json();
// {
// "data": [
// {
// "id": "wh_abc123",
// "url": "https://your-app.com/api/webhook",
// "events": ["usage.threshold", "payment.completed"],
// "active": true
// }
// ]
// }
// Delete a webhook
await fetch('https://api.llmhub.dev/v1/webhooks/wh_abc123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer your-api-key' }
});
// Update a webhook
await fetch('https://api.llmhub.dev/v1/webhooks/wh_abc123', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: ['usage.threshold'], // Update events
active: false // Disable webhook
})
});Retry Policy
If your endpoint doesn't return a 2xx response, we'll retry the webhook:
- • Attempt 1: Immediate
- • Attempt 2: After 1 minute
- • Attempt 3: After 5 minutes
- • Attempt 4: After 30 minutes
- • Attempt 5: After 2 hours
- • Attempt 6: After 8 hours (final)
After all retries fail, the event is marked as failed. You can view failed events in the dashboard.
Best Practices
Always Verify Signatures
Never trust webhook payloads without verifying the HMAC signature. This prevents spoofed requests.
Respond Quickly
Return a 200 response within 30 seconds. Process events asynchronously if needed to avoid timeouts.
Handle Duplicates
Use the event id to deduplicate. The same event may be delivered multiple times during retries.
Use HTTPS
Webhook endpoints must use HTTPS in production to ensure payload confidentiality.
Monitor Your Endpoints
Set up alerts for webhook failures. Check the dashboard for failed deliveries.
Testing Webhooks
Use a Tunnel for Local Dev
Tools like ngrok or localtunnel expose your localhost to receive webhooks during development.
Test from Dashboard
Use the "Send Test Event" button in the webhook settings to trigger sample events.

