Webhook Integration
Learn how to receive real-time notifications about your usage and alerts via HTTP callbacks.
Overview
Webhooks allow you to receive real-time HTTP notifications when specific events occur in your Clauder Limiter account. Instead of polling our API for updates, webhooks push data to your server as events happen.
Common use cases include:
- Sending Slack or Discord notifications when approaching usage limits
- Triggering automated workflows when limits are reached
- Syncing usage data with external dashboards or billing systems
- Creating custom alerting pipelines
Configure your webhooks in the Settings page.
Event Types
Subscribe to one or more of the following event types:
| Event | Description |
|---|---|
usage_warning | Triggered when usage approaches threshold (50%, 75%, 90%) |
limit_reached | Triggered when usage limit is reached |
daily_summary | Daily usage statistics digest (sent at midnight UTC) |
weekly_report | Weekly usage and cost report (sent on Mondays) |
Payload Format
All webhook payloads follow a consistent JSON structure:
{
"id": "evt_abc123xyz789",
"type": "usage_warning",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"threshold": 75,
"current_usage": 7500,
"limit": 10000,
"percentage": 75.0,
"period": "monthly"
}
}Common Fields
id- Unique event identifiertype- The event type (matches your subscription)created_at- ISO 8601 timestampdata- Event-specific payload data
Signature Verification
Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Always verify this signature to ensure the request originated from Clauder Limiter.
Security Note: Never skip signature verification in production. Unsigned or incorrectly signed requests should be rejected with a 401 status.
Signature Format
The signature header format is:
X-Webhook-Signature: sha256=<hmac_hex_digest>Verification Steps
- Extract the signature from the
X-Webhook-Signatureheader - Remove the
sha256=prefix - Compute HMAC-SHA256 of the raw request body using your signing secret
- Compare the computed signature with the header value using a timing-safe comparison
Implementation Examples
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const providedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(providedSignature, 'hex')
);
}import hmac
import hashlib
def verify_webhook_signature(raw_body, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
provided_signature = signature.replace('sha256=', '')
return hmac.compare_digest(expected_signature, provided_signature)package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyWebhookSignature(rawBody []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expectedSignature := hex.EncodeToString(mac.Sum(nil))
providedSignature := strings.TrimPrefix(signature, "sha256=")
return hmac.Equal(
[]byte(expectedSignature),
[]byte(providedSignature),
)
}Retry Policy
If your endpoint returns a non-2xx status code or times out, we'll retry the delivery using exponential backoff.
| Attempt | Delay After Failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry (final) | 24 hours |
After 5 failed attempts, the webhook delivery is marked as failed and no further retries are attempted. You can view delivery history and manually retry failed deliveries from the Settings page.
Best Practices
- Return a 2xx status quickly (within 5 seconds)
- Process webhook data asynchronously if needed
- Implement idempotency using the event
id - Store the raw payload for debugging
Code Examples
Complete endpoint examples for popular frameworks.
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Use raw body for signature verification
app.post('/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const providedSignature = signature.replace('sha256=', '');
const isValid = crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(providedSignature, 'hex')
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse and process the event
const event = JSON.parse(req.body.toString());
switch (event.type) {
case 'usage_warning':
console.log(`Usage at ${event.data.percentage}%`);
// Handle warning...
break;
case 'limit_reached':
console.log('Limit reached!');
// Handle limit...
break;
}
res.status(200).json({ received: true });
}
);
app.listen(3000);from flask import Flask, request, jsonify
import hmac
import hashlib
import os
import json
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
if not signature:
return jsonify({'error': 'Missing signature'}), 401
# Verify signature
raw_body = request.get_data()
expected_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
provided_signature = signature.replace('sha256=', '')
if not hmac.compare_digest(expected_signature, provided_signature):
return jsonify({'error': 'Invalid signature'}), 401
# Parse and process the event
event = json.loads(raw_body)
if event['type'] == 'usage_warning':
print(f"Usage at {event['data']['percentage']}%")
# Handle warning...
elif event['type'] == 'limit_reached':
print('Limit reached!')
# Handle limit...
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"strings"
)
var webhookSecret = os.Getenv("WEBHOOK_SECRET")
type WebhookEvent struct {
ID string `json:"id"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
Data map[string]interface{} `json:"data"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Webhook-Signature")
if signature == "" {
http.Error(w, "Missing signature", http.StatusUnauthorized)
return
}
rawBody, _ := io.ReadAll(r.Body)
// Verify signature
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(rawBody)
expectedSig := hex.EncodeToString(mac.Sum(nil))
providedSig := strings.TrimPrefix(signature, "sha256=")
if !hmac.Equal([]byte(expectedSig), []byte(providedSig)) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var event WebhookEvent
json.Unmarshal(rawBody, &event)
switch event.Type {
case "usage_warning":
log.Printf("Usage at %.0f%%", event.Data["percentage"])
case "limit_reached":
log.Println("Limit reached!")
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhook", webhookHandler)
log.Fatal(http.ListenAndServe(":3000", nil))
}Need Help?
Having trouble integrating webhooks? Check out our support page or report an issue.