Developer Docs

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:

EventDescription
usage_warningTriggered when usage approaches threshold (50%, 75%, 90%)
limit_reachedTriggered when usage limit is reached
daily_summaryDaily usage statistics digest (sent at midnight UTC)
weekly_reportWeekly usage and cost report (sent on Mondays)

Payload Format

All webhook payloads follow a consistent JSON structure:

JSON
{
  "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 identifier
  • type - The event type (matches your subscription)
  • created_at - ISO 8601 timestamp
  • data - 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:

Plain Text
X-Webhook-Signature: sha256=<hmac_hex_digest>

Verification Steps

  1. Extract the signature from the X-Webhook-Signature header
  2. Remove the sha256= prefix
  3. Compute HMAC-SHA256 of the raw request body using your signing secret
  4. 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.

AttemptDelay After Failure
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 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.