DOCS

Webhooks

Get real-time notifications when events happen in your Skimly account. Webhooks are essential for building integrations, monitoring usage, and keeping your systems in sync.

Why Use Webhooks?

Real-time updates: Get notified instantly when chats complete, blobs are created, or usage changes.
Build integrations: Connect Skimly to your existing systems and workflows.
Monitor usage: Track costs and token usage in real-time.
Audit trail: Keep a complete record of all activities.

1. Setup Local Development

To receive webhooks locally, you need to create a secure tunnel to your localhost. We recommend ngrok or cloudflared for development.

ngrok (Recommended)

# Install ngrok
npm install -g ngrok

# Create tunnel to your local server
ngrok http 8000

# Use the https URL in Skimly Settings

cloudflared (Alternative)

# Install cloudflared
brew install cloudflare/cloudflare/cloudflared

# Create tunnel
cloudflared tunnel --url http://localhost:8000
Security Note

Never expose webhook endpoints publicly without proper authentication. Use ngrok/cloudflared for development only. In production, deploy to a secure HTTPS endpoint.

2. Subscribe to Webhooks

Once you have a public URL, subscribe to webhook events. You can subscribe to specific event types or receive all events.

Subscribe to All Events

curl http://localhost:8000/api/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://abc123.ngrok.io/webhooks"}'

Subscribe to Specific Events

curl http://localhost:8000/api/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks",
    "events": ["chat.completed", "blob.created"]
  }'

SDK Examples

Use our official SDKs for easier webhook management:

Node.js

import { SkimlyClient } from "@skimly/sdk"

const client = SkimlyClient.fromEnv()

// Subscribe to webhooks
await client.webhooks.create({
  url: "https://abc123.ngrok.io/webhooks",
  events: ["chat.completed", "blob.created"]
})

// List webhooks
const webhooks = await client.webhooks.list()

// Delete webhook
await client.webhooks.delete("wh_123")

Python

from skimly import SkimlyClient

client = SkimlyClient.from_env()

# Subscribe to webhooks
client.webhooks.create(
    url="https://abc123.ngrok.io/webhooks",
    events=["chat.completed", "blob.created"]
)

# List webhooks
webhooks = client.webhooks.list()

# Delete webhook
client.webhooks.delete("wh_123")

3. Handle Webhook Events

Your webhook endpoint will receive POST requests with event data. Always verify the signature to ensure the webhook came from Skimly.

Node.js Examples

Express.js

import express from "express"
import { verifyWebhook } from "@skimly/sdk"

const app = express()

// Important: Use raw body parsing for webhook verification
app.post("/webhooks", express.raw({type: 'application/json'}), (req, res) => {
  try {
    const event = verifyWebhook(req.body, req.headers['skimly-signature'])
    
    switch (event.type) {
      case 'chat.completed':
        console.log('Chat finished:', {
          requestId: event.data.request_id,
          tokensSaved: event.data.tokens_saved,
          costUsd: event.data.cost_usd
        })
        break
        
      case 'blob.created':
        console.log('Blob created:', {
          blobId: event.data.blob_id,
          sizeBytes: event.data.size_bytes
        })
        break
        
      case 'usage.updated':
        console.log('Usage updated:', {
          organizationId: event.data.organization_id,
          totalTokens: event.data.total_tokens
        })
        break
    }
    
    res.sendStatus(200)
  } catch (error) {
    console.error('Webhook verification failed:', error)
    res.status(401).json({error: 'Invalid signature'})
  }
})

Fastify

import Fastify from "fastify"
import { verifyWebhook } from "@skimly/sdk"

const fastify = Fastify()

fastify.post("/webhooks", {
  config: {
    rawBody: true
  }
}, async (request, reply) => {
  try {
    const event = verifyWebhook(request.rawBody, request.headers['skimly-signature'])
    
    if (event.type === 'chat.completed') {
      console.log('Chat completed:', event.data)
    }
    
    return { received: true }
  } catch (error) {
    reply.code(401)
    return { error: 'Invalid signature' }
  }
})

Python Examples

Flask

from flask import Flask, request
from skimly import verify_webhook

app = Flask(__name__)

@app.route("/webhooks", methods=["POST"])
def webhook():
    try:
        event = verify_webhook(
            request.get_data(),
            request.headers.get("skimly-signature")
        )
        
        if event["type"] == "chat.completed":
            print("Chat completed:", {
                "request_id": event["data"]["request_id"],
                "tokens_saved": event["data"]["tokens_saved"],
                "cost_usd": event["data"]["cost_usd"]
            })
        elif event["type"] == "blob.created":
            print("Blob created:", {
                "blob_id": event["data"]["blob_id"],
                "size_bytes": event["data"]["size_bytes"]
            })
        
        return "", 200
    except Exception as e:
        print(f"Webhook verification failed: {e}")
        return {"error": "Invalid signature"}, 401

FastAPI

from fastapi import FastAPI, Request, HTTPException
from skimly import verify_webhook

app = FastAPI()

@app.post("/webhooks")
async def webhook(request: Request):
    try:
        body = await request.body()
        signature = request.headers.get("skimly-signature")
        
        event = verify_webhook(body, signature)
        
        if event["type"] == "chat.completed":
            print(f"Chat completed: {event['data']}")
        elif event["type"] == "blob.created":
            print(f"Blob created: {event['data']}")
        
        return {"received": True}
    except Exception as e:
        raise HTTPException(status_code=401, detail="Invalid signature")
Important: Raw Body Parsing

Always use raw body parsing for webhook endpoints. The signature verification requires the exact bytes that were sent. Express.js users should use express.raw(), Fastify users should enable rawBody: true.

Event Types

Skimly sends different types of events depending on what happened in your account. Each event contains relevant data and metadata.

chat.completed

Fired when a chat request finishes processing

request_idUnique identifier for the request
providerWhich provider was used (openai/anthropic)
modelModel name (e.g., gpt-4o-mini)
tokens_inInput tokens sent to provider
tokens_outOutput tokens from provider
tokens_savedTokens avoided via blobbing
cost_usdActual cost of the request
user_idUser who made the request

blob.created

Fired when new content is uploaded as a blob

blob_idUnique blob identifier
content_hashSHA256 hash of the content
size_bytesSize of the content in bytes
mime_typeContent type (e.g., text/plain)
user_idUser who created the blob
organization_idOrganization that owns the blob

usage.updated

Fired when organization usage metrics change

organization_idAffected organization
periodBilling period (e.g., "2024-01")
total_tokensTotal tokens used in period
total_cost_usdTotal cost in USD
tokens_savedTokens saved via blobbing
cost_savings_usdCost savings in USD

4. Testing Webhooks

Test your webhook endpoints locally to ensure they're working correctly before deploying to production.

Test with cURL

# Test your endpoint locally
curl -X POST http://localhost:8000/webhooks \
  -H "Content-Type: application/json" \
  -d '{"type":"test","data":{"message":"Hello webhook!"}}'

Monitor Logs

# Watch your server logs
npm run dev

# Or for Python
python app.py

# Look for webhook requests in the console

5. Production Deployment

When deploying to production, ensure your webhook endpoint is secure and reliable.

Security Checklist

  • ✅ Use HTTPS endpoints only
  • ✅ Verify webhook signatures
  • ✅ Implement rate limiting
  • ✅ Use environment variables for secrets
  • ✅ Monitor for suspicious activity

Reliability Checklist

  • ✅ Return 200 status quickly
  • ✅ Process events asynchronously
  • ✅ Implement retry logic
  • ✅ Monitor webhook delivery
  • ✅ Have fallback mechanisms
Ready to implement webhooks?

Follow the steps above to set up webhooks for your Skimly integration. Start with local development using ngrok, then deploy to production when ready.

Next Steps

API Reference

Complete endpoint documentation with all available options and parameters.

View API docs →

Quickstart Guide

Get up and running with Skimly in minutes with SDK examples.

View quickstart →