| name | openevidence-webhooks-events |
| description | Configure OpenEvidence webhooks for async DeepConsult completion and events.
Use when implementing webhook handlers, configuring async notifications,
or setting up event-driven clinical AI workflows.
Trigger with phrases like "openevidence webhook", "openevidence events",
"deepconsult callback", "openevidence notifications".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
OpenEvidence Webhooks & Events
Overview
Configure webhooks for asynchronous OpenEvidence operations like DeepConsult completion notifications.
Prerequisites
- OpenEvidence Enterprise account with webhook access
- HTTPS endpoint for receiving webhooks
- Webhook secret for signature verification
- Understanding of async processing patterns
Webhook Events
| Event Type | Description | Payload |
|---|
deepconsult.started | DeepConsult processing began | consultId, estimatedTime |
deepconsult.progress | Processing progress update | consultId, progress (0-100) |
deepconsult.completed | DeepConsult finished successfully | consultId, report |
deepconsult.failed | DeepConsult processing failed | consultId, error |
rate_limit.warning | Approaching rate limit | remaining, limit, resetAt |
api_key.expiring | API key expiration warning | keyId, expiresAt |
Instructions
Step 1: Configure Webhook Endpoint
import { Router, Request, Response } from 'express';
import crypto from 'crypto';
const router = Router();
const WEBHOOK_SECRET = process.env.OPENEVIDENCE_WEBHOOK_SECRET!;
interface WebhookPayload {
id: string;
event: string;
timestamp: string;
data: any;
}
function verifySignature(req: Request, res: Response, next: Function) {
const signature = req.headers['x-openevidence-signature'] as string;
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
const parts = signature.().( {
[key, value] = part.();
acc[key] = value;
acc;
}, {} <, >);
timestamp = (parts[]);
providedSig = parts[];
now = .(.() / );
(.(now - timestamp) > ) {
res.().({ : });
}
payload = req. === ? req. : .(req.);
signedPayload = ;
expectedSig = crypto
.(, )
.(signedPayload)
.();
{
valid = crypto.(
.(providedSig),
.(expectedSig)
);
(!valid) ();
} {
res.().({ : });
}
();
}
router.(
,
verifySignature,
(: , : ) => {
: = req.;
.();
{
(payload);
res.().({ : });
} (: ) {
.(, error);
res.().({ : , : error. });
}
}
);
router;
Step 2: Event Handlers
import { WebhookPayload } from './types';
import { notificationService } from '../services/notifications';
import { db } from '../db';
export async function handleWebhookEvent(payload: WebhookPayload): Promise<void> {
switch (payload.event) {
case 'deepconsult.started':
await handleDeepConsultStarted(payload);
break;
case 'deepconsult.progress':
await handleDeepConsultProgress(payload);
break;
case 'deepconsult.completed':
await handleDeepConsultCompleted(payload);
break;
case 'deepconsult.failed':
await handleDeepConsultFailed(payload);
break;
case 'rate_limit.warning':
await handleRateLimitWarning(payload);
break;
case 'api_key.expiring':
(payload);
;
:
.();
}
}
(): <> {
{ consultId, estimatedTime } = payload.;
db..({
: { consultId },
: {
: ,
: (.() + estimatedTime * ),
},
});
}
(): <> {
{ consultId, progress, currentPhase } = payload.;
db..({
: { consultId },
: {
progress,
currentPhase,
},
});
consult = db..({ : { consultId } });
(consult?.) {
notificationService.(consult., consultId, progress);
}
}
(): <> {
{ consultId, report } = payload.;
db..({
: { consultId },
: {
: ,
report,
: (),
},
});
consult = db..({
: { consultId },
: { : },
});
(consult) {
notificationService.({
: consult.,
: ,
: ,
: ,
: { consultId },
});
(consult..) {
notificationService.({
: consult..,
: ,
: ,
: {
: consult..,
consultId,
: report..(, ),
},
});
}
}
}
(): <> {
{ consultId, error, retryable } = payload.;
db..({
: { consultId },
: {
: ,
error,
retryable,
},
});
notificationService.({
: ,
: ,
: ,
retryable,
});
}
(): <> {
{ remaining, limit, resetAt } = payload.;
.();
(remaining < limit * ) {
notificationService.({
: ,
: ,
: ,
});
}
}
(): <> {
{ keyId, expiresAt } = payload.;
notificationService.({
: ,
: ,
: ,
: ,
});
}
Step 3: Webhook Registration
import { OpenEvidenceClient } from '@openevidence/sdk';
export async function registerWebhooks(): Promise<void> {
const client = new OpenEvidenceClient({
apiKey: process.env.OPENEVIDENCE_API_KEY!,
orgId: process.env.OPENEVIDENCE_ORG_ID!,
});
const webhookUrl = process.env.WEBHOOK_BASE_URL + '/webhooks/openevidence';
await client.webhooks.register({
url: webhookUrl,
events: [
'deepconsult.started',
'deepconsult.progress',
'deepconsult.completed',
'deepconsult.failed',
'rate_limit.warning',
'api_key.expiring',
],
secret: process.env.OPENEVIDENCE_WEBHOOK_SECRET!,
});
console.log(`[Webhooks] Registered: ${webhookUrl}`);
}
registerWebhooks().catch(console.error);
Step 4: Idempotency Handling
import { db } from '../db';
const IDEMPOTENCY_TTL = 24 * 60 * 60 * 1000;
export async function isProcessed(webhookId: string): Promise<boolean> {
const existing = await db.processedWebhooks.findUnique({
where: { id: webhookId },
});
return !!existing;
}
export async function markProcessed(webhookId: string): Promise<void> {
await db.processedWebhooks.create({
data: {
id: webhookId,
processedAt: new Date(),
expiresAt: new Date(Date.now() + IDEMPOTENCY_TTL),
},
});
}
export async (): <> {
db..({
: {
: { : () },
},
});
}
(): <> {
( (payload.)) {
.();
;
}
(payload);
(payload.);
}
Step 5: Webhook Testing
import { describe, it, expect, vi, beforeEach } from 'vitest';
import request from 'supertest';
import crypto from 'crypto';
import app from '../../src/app';
const WEBHOOK_SECRET = 'test-secret';
process.env.OPENEVIDENCE_WEBHOOK_SECRET = WEBHOOK_SECRET;
function generateSignature(payload: object, timestamp: number): string {
const payloadString = JSON.stringify(payload);
const signedPayload = `${timestamp}.${payloadString}`;
const signature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signedPayload)
.digest('hex');
return `t=${timestamp},v1=${signature}`;
}
describe('OpenEvidence Webhooks', () => {
it('should accept valid webhook', () => {
timestamp = .(.() / );
payload = {
: ,
: ,
: ().(),
: {
: ,
: { : },
},
};
response = (app)
.()
.(, (payload, timestamp))
.(payload);
(response.).();
(response..).();
});
(, () => {
payload = { : };
response = (app)
.()
.(, )
.(payload);
(response.).();
});
(, () => {
oldTimestamp = .(.() / ) - ;
payload = { : };
response = (app)
.()
.(, (payload, oldTimestamp))
.(payload);
(response.).();
});
});
Output
- Secure webhook endpoint with signature verification
- Event handlers for all OpenEvidence events
- Idempotency protection
- Notification integration
- Comprehensive test coverage
Webhook Security Checklist
Error Handling
| Webhook Issue | Detection | Resolution |
|---|
| Invalid signature | 401 response | Check secret configuration |
| Missing events | No handler called | Verify webhook registration |
| Duplicate processing | Multiple notifications | Enable idempotency |
| Timeout | Webhook fails | Process async, return 200 quickly |
Resources
Next Steps
For performance optimization, see openevidence-performance-tuning.