Webhooks
Receive real-time notifications when events occur in your Check account.
Webhooks
Webhooks allow you to receive real-time HTTP callbacks when events occur in your account. Instead of polling the API, webhooks push data to your server as events happen.
Setting Up Webhooks
Via Dashboard
- Navigate to Dashboard → Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS in production)
- Select the events you want to subscribe to
- Copy the signing secret for signature verification
- Save the webhook
Webhook Events
| Event | Description |
|---|---|
verification.completed | A verification has finished processing |
verification.failed | A verification encountered an error |
verification.escalated | A verification was escalated for human review |
escalation.resolved | An escalation was resolved by a reviewer |
batch.completed | A batch job has finished processing |
batch.failed | A batch job encountered an error |
usage.limit.warning | Approaching monthly usage limit (80%) |
usage.limit.reached | Monthly usage limit reached |
Webhook Payload
All webhooks send a POST request with a JSON payload:
{
"id": "evt_abc123def456",
"type": "verification.completed",
"createdAt": "2024-01-15T10:30:02Z",
"data": {
"verificationId": "ver_xyz789",
"status": "completed",
"verdict": "true",
"confidence": 0.95,
"decision": "accept",
"reasoning": "The claim is factually accurate.",
"paradigmResults": [
{
"paradigm": "reasoning",
"verdict": "true",
"confidence": 0.94
},
{
"paradigm": "tool",
"verdict": "true",
"confidence": 0.97
}
]
}
}Payload Fields
| Parameter | Type | Description |
|---|
Verifying Signatures
Every webhook request includes a signature header that you should verify to ensure the request came from Check. This prevents malicious actors from sending fake webhook events.
Signature Header
X-Check-Signature: t=1705317002,v1=5257a869e7ecebeda32affa62cdca3fa51cef28a...
The header contains:
t- Unix timestamp when the signature was generatedv1- HMAC-SHA256 signature of the payload
Verification Steps
- Extract the timestamp (
t) and signature (v1) from the header - Construct the signed payload:
${timestamp}.${rawBody} - Compute HMAC-SHA256 using your webhook signing secret
- Compare with the provided signature using constant-time comparison
- Verify the timestamp is recent (within 5 minutes)
Handling Webhooks
Example Endpoint (Next.js)
// app/api/webhooks/check/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.CHECK_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const signature = request.headers.get('X-Check-Signature');
const body = await request.text();
if (!signature) {
return NextResponse.json({ error: 'Missing signature' }, { status: 401 });
}
// Verify signature
const { valid, error } = verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
if (!valid) {
console.error('Webhook verification failed:', error);
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
// Parse and handle event
const event = JSON.parse(body);
switch (event.type) {
case 'verification.completed':
await handleVerificationComplete(event.data);
break;
case 'verification.escalated':
await handleEscalation(event.data);
break;
case 'batch.completed':
await handleBatchComplete(event.data);
break;
case 'usage.limit.warning':
await handleUsageWarning(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
// Return 200 to acknowledge receipt
return NextResponse.json({ received: true });
}
async function handleVerificationComplete(data: any) {
console.log('Verification completed:', data.verificationId);
// Update your database, notify users, etc.
}
async function handleEscalation(data: any) {
console.log('Verification escalated:', data.verificationId);
// Alert your review team
}
async function handleBatchComplete(data: any) {
console.log('Batch completed:', data.batchId);
// Process batch results
}
async function handleUsageWarning(data: any) {
console.log('Usage warning:', data.usedPercentage);
// Alert administrators
}Example Endpoint (Express)
import express from 'express';
const app = express();
// Important: Use raw body for signature verification
app.post(
'/webhooks/check',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-check-signature'] as string;
const body = req.body.toString();
const { valid } = verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
if (!valid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(body);
// Handle event asynchronously
processWebhookEvent(event).catch(console.error);
// Return 200 immediately
res.json({ received: true });
}
);Example Endpoint (Python/FastAPI)
from fastapi import FastAPI, Request, HTTPException
import json
app = FastAPI()
@app.post("/webhooks/check")
async def handle_webhook(request: Request):
signature = request.headers.get("X-Check-Signature")
body = await request.body()
body_str = body.decode()
if not signature:
raise HTTPException(status_code=401, detail="Missing signature")
valid, error = verify_webhook_signature(body_str, signature, WEBHOOK_SECRET)
if not valid:
raise HTTPException(status_code=401, detail=error)
event = json.loads(body_str)
match event["type"]:
case "verification.completed":
await handle_verification_complete(event["data"])
case "verification.escalated":
await handle_escalation(event["data"])
case "batch.completed":
await handle_batch_complete(event["data"])
return {"received": True}Best Practices
Respond Quickly
Return a 200 status code immediately after receiving the webhook. Process the event asynchronously if needed.
// Queue for async processing
await queue.add('process-webhook', event);
return NextResponse.json({ received: true });Handle Duplicates
Webhooks may occasionally be delivered more than once. Use the id field to deduplicate:
const processed = await db.webhookEvents.findUnique({
where: { eventId: event.id }
});
if (processed) {
return NextResponse.json({ received: true }); // Already processed
}
// Mark as processed before handling
await db.webhookEvents.create({ data: { eventId: event.id } });Retry Logic
Check retries failed webhooks with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 24 hours |
After 5 failed attempts, the webhook is marked as failed and appears in your dashboard for manual inspection.
Endpoint Requirements
Your webhook endpoint must:
- Accept HTTPS connections (HTTP not supported in production)
- Respond within 30 seconds
- Return a 2xx status code on success
- Be publicly accessible (not behind VPN/firewall)
Testing Webhooks
Dashboard Testing
Use the Test button in Dashboard → Settings → Webhooks to send a test event to your endpoint.
Local Development
Use tools like ngrok or localtunnel to expose your local development server:
# Install ngrok
npm install -g ngrok
# Expose local port 3000
ngrok http 3000
# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/api/webhooks/checkWebhook Logs
View delivery history and debug failed webhooks in Dashboard → Settings → Webhooks → Delivery Logs. Each log entry shows:
- Timestamp
- Event type
- HTTP status code
- Response body
- Retry count
Event-Specific Payloads
verification.completed
{
"verificationId": "ver_abc123",
"status": "completed",
"verdict": "true",
"confidence": 0.95,
"decision": "accept",
"reasoning": "The claim is factually accurate.",
"paradigmResults": [...],
"processingTimeMs": 1850
}verification.escalated
{
"verificationId": "ver_abc123",
"escalationId": "esc_xyz789",
"priority": "high",
"reason": "Low confidence score (0.52) with conflicting paradigm results",
"deadline": "2024-01-16T10:30:00Z"
}batch.completed
{
"batchId": "batch_abc123",
"status": "completed",
"totalItems": 100,
"successCount": 98,
"failureCount": 2,
"processingTimeMs": 45000
}usage.limit.warning
{
"organizationId": "org_abc123",
"currentUsage": 800,
"limit": 1000,
"usedPercentage": 80,
"resetDate": "2024-02-01T00:00:00Z"
}