Claude-skill-registry exa-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/exa-webhooks-events" ~/.claude/skills/majiayu000-claude-skill-registry-exa-webhooks-events-2b9378 && rm -rf "$T"
manifest:
skills/data/exa-webhooks-events/SKILL.mdsource content
Exa Webhooks & Events
Overview
Securely handle Exa webhooks with signature validation and replay protection.
Prerequisites
- Exa webhook secret configured
- HTTPS endpoint accessible from internet
- Understanding of cryptographic signatures
- Redis or database for idempotency (optional)
Webhook Endpoint Setup
Express.js
import express from 'express'; import crypto from 'crypto'; const app = express(); // IMPORTANT: Raw body needed for signature verification app.post('/webhooks/exa', express.raw({ type: 'application/json' }), async (req, res) => { const signature = req.headers['x-exa-signature'] as string; const timestamp = req.headers['x-exa-timestamp'] as string; if (!verifyExaSignature(req.body, signature, timestamp)) { return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString()); await handleExaEvent(event); res.status(200).json({ received: true }); } );
Signature Verification
function verifyExaSignature( payload: Buffer, signature: string, timestamp: string ): boolean { const secret = process.env.EXA_WEBHOOK_SECRET!; // Reject old timestamps (replay attack protection) const timestampAge = Date.now() - parseInt(timestamp) * 1000; if (timestampAge > 300000) { // 5 minutes console.error('Webhook timestamp too old'); return false; } // Compute expected signature const signedPayload = `${timestamp}.${payload.toString()}`; const expectedSignature = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex'); // Timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); }
Event Handler Pattern
type ExaEventType = 'resource.created' | 'resource.updated' | 'resource.deleted'; interface ExaEvent { id: string; type: ExaEventType; data: Record<string, any>; created: string; } const eventHandlers: Record<ExaEventType, (data: any) => Promise<void>> = { 'resource.created': async (data) => { /* handle */ }, 'resource.updated': async (data) => { /* handle */ }, 'resource.deleted': async (data) => { /* handle */ } }; async function handleExaEvent(event: ExaEvent): Promise<void> { const handler = eventHandlers[event.type]; if (!handler) { console.log(`Unhandled event type: ${event.type}`); return; } try { await handler(event.data); console.log(`Processed ${event.type}: ${event.id}`); } catch (error) { console.error(`Failed to process ${event.type}: ${event.id}`, error); throw error; // Rethrow to trigger retry } }
Idempotency Handling
import { Redis } from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); async function isEventProcessed(eventId: string): Promise<boolean> { const key = `exa:event:${eventId}`; const exists = await redis.exists(key); return exists === 1; } async function markEventProcessed(eventId: string): Promise<void> { const key = `exa:event:${eventId}`; await redis.set(key, '1', 'EX', 86400 * 7); // 7 days TTL }
Webhook Testing
# Use Exa CLI to send test events exa webhooks trigger resource.created --url http://localhost:3000/webhooks/exa # Or use webhook.site for debugging curl -X POST https://webhook.site/your-uuid \ -H "Content-Type: application/json" \ -d '{"type": "resource.created", "data": {}}'
Instructions
Step 1: Register Webhook Endpoint
Configure your webhook URL in the Exa dashboard.
Step 2: Implement Signature Verification
Use the signature verification code to validate incoming webhooks.
Step 3: Handle Events
Implement handlers for each event type your application needs.
Step 4: Add Idempotency
Prevent duplicate processing with event ID tracking.
Output
- Secure webhook endpoint
- Signature validation enabled
- Event handlers implemented
- Replay attack protection active
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong secret | Verify webhook secret |
| Timestamp rejected | Clock drift | Check server time sync |
| Duplicate events | Missing idempotency | Implement event ID tracking |
| Handler timeout | Slow processing | Use async queue |
Examples
Testing Webhooks Locally
# Use ngrok to expose local server ngrok http 3000 # Send test webhook curl -X POST https://your-ngrok-url/webhooks/exa \ -H "Content-Type: application/json" \ -d '{"type": "test", "data": {}}'
Resources
Next Steps
For performance optimization, see
exa-performance-tuning.