Webhooks API
SmartCV Webhooks API documentation, learn how to receive real-time event notifications
Webhooks allow your application to receive real-time notifications about events that occur in SmartCV, without the need to continuously poll our API.
🔔 Overview
When specific events occur in SmartCV, we send HTTP POST requests to your configured endpoint. This allows you to:
- Synchronize data in real-time
- Automatically trigger workflows
- Monitor important activities
- Integrate third-party services
📋 Supported Events
Resume-Related Events
| Event Type | Description |
|---|---|
resume.created | Resume created |
resume.updated | Resume updated |
resume.deleted | Resume deleted |
resume.shared | Resume shared |
resume.exported | Resume exported |
User-Related Events
| Event Type | Description |
|---|---|
user.registered | User registered |
user.subscription_changed | Subscription status changed |
user.profile_updated | User profile updated |
AI-Related Events
| Event Type | Description |
|---|---|
ai.analysis_completed | AI analysis completed |
ai.content_generated | AI content generation completed |
🔧 Configuring Webhooks
Configuration via API
Endpoint: POST /api/webhooks
Request Body:
{
"url": "https://yourapp.com/webhooks/smartcv",
"events": ["resume.created", "resume.updated", "ai.analysis_completed"],
"secret": "your_webhook_secret",
"active": true
}Response:
{
"success": true,
"data": {
"id": "webhook_123",
"url": "https://yourapp.com/webhooks/smartcv",
"events": ["resume.created", "resume.updated", "ai.analysis_completed"],
"secret": "your_webhook_secret",
"active": true,
"createdAt": "2024-12-30T10:00:00Z"
}
}Managing Webhooks
// Get webhooks list
async function getWebhooks() {
const response = await fetch('/api/webhooks', {
credentials: 'include'
})
return response.json()
}
// Update webhook
async function updateWebhook(webhookId, updates) {
const response = await fetch(`/api/webhooks/${webhookId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(updates)
})
return response.json()
}
// Delete webhook
async function deleteWebhook(webhookId) {
const response = await fetch(`/api/webhooks/${webhookId}`, {
method: 'DELETE',
credentials: 'include'
})
return response.json()
}📨 Webhook Payload
Payload Structure
All webhook requests contain the following structure:
{
"id": "event_123",
"type": "resume.created",
"created": "2024-12-30T10:00:00Z",
"data": {
/* Event-specific data */
},
"api_version": "2024-12-30"
}Event Examples
resume.created
{
"id": "event_123",
"type": "resume.created",
"created": "2024-12-30T10:00:00Z",
"data": {
"resume": {
"id": "resume_456",
"title": "Software Engineer Resume",
"status": "DRAFT",
"user_id": "user_789",
"template_id": "template_123",
"created_at": "2024-12-30T10:00:00Z"
}
},
"api_version": "2024-12-30"
}ai.analysis_completed
{
"id": "event_124",
"type": "ai.analysis_completed",
"created": "2024-12-30T10:05:00Z",
"data": {
"analysis": {
"resume_id": "resume_456",
"overall_score": 85,
"keyword_match_score": 78,
"format_score": 92,
"content_score": 88,
"suggestions_count": 5
}
},
"api_version": "2024-12-30"
}user.subscription_changed
{
"id": "event_125",
"type": "user.subscription_changed",
"created": "2024-12-30T10:10:00Z",
"data": {
"user": {
"id": "user_789",
"email": "user@example.com"
},
"subscription": {
"previous_type": "FREE",
"current_type": "PREMIUM",
"changed_at": "2024-12-30T10:10:00Z"
}
},
"api_version": "2024-12-30"
}🔐 Security Verification
Signature Verification
SmartCV uses HMAC SHA256 signatures to ensure webhook security.
Request Headers:
X-SmartCV-Signature: sha256=<signature>
X-SmartCV-Timestamp: <timestamp>
Verification Examples
Node.js
const crypto = require('crypto')
function verifyWebhookSignature(payload, signature, secret, timestamp) {
// Check timestamp (prevent replay attacks)
const currentTime = Math.floor(Date.now() / 1000)
const webhookTime = parseInt(timestamp)
if (Math.abs(currentTime - webhookTime) > 300) {
// Valid within 5 minutes
throw new Error('Webhook timestamp too old')
}
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex')
const actualSignature = signature.replace('sha256=', '')
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature, 'hex'), Buffer.from(actualSignature, 'hex'))) {
throw new Error('Invalid webhook signature')
}
return true
}
// Express middleware example
app.post('/webhooks/smartcv', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body
const signature = req.headers['x-smartcv-signature']
const timestamp = req.headers['x-smartcv-timestamp']
try {
verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)
const event = JSON.parse(payload)
handleWebhookEvent(event)
res.status(200).send('OK')
} catch (error) {
console.error('Webhook verification failed:', error)
res.status(400).send('Invalid webhook')
}
})Python
import hmac
import hashlib
import time
import json
def verify_webhook_signature(payload, signature, secret, timestamp):
# Check timestamp
current_time = int(time.time())
webhook_time = int(timestamp)
if abs(current_time - webhook_time) > 300: # Valid within 5 minutes
raise ValueError("Webhook timestamp too old")
# Verify signature
expected_signature = hmac.new(
secret.encode('utf-8'),
f"{timestamp}.{payload}".encode('utf-8'),
hashlib.sha256
).hexdigest()
actual_signature = signature.replace('sha256=', '')
if not hmac.compare_digest(expected_signature, actual_signature):
raise ValueError("Invalid webhook signature")
return True
# Flask example
from flask import Flask, request, jsonify
@app.route('/webhooks/smartcv', methods=['POST'])
def handle_webhook():
payload = request.get_data(as_text=True)
signature = request.headers.get('X-SmartCV-Signature')
timestamp = request.headers.get('X-SmartCV-Timestamp')
try:
verify_webhook_signature(payload, signature, WEBHOOK_SECRET, timestamp)
event = json.loads(payload)
handle_webhook_event(event)
return jsonify({'status': 'success'}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400📝 Event Handling Examples
Complete Webhook Handler
class SmartCVWebhookHandler {
constructor(secret) {
this.secret = secret
}
async handleWebhook(payload, signature, timestamp) {
// Verify signature
this.verifySignature(payload, signature, timestamp)
const event = JSON.parse(payload)
// Dispatch handling based on event type
switch (event.type) {
case 'resume.created':
await this.handleResumeCreated(event.data)
break
case 'resume.updated':
await this.handleResumeUpdated(event.data)
break
case 'ai.analysis_completed':
await this.handleAnalysisCompleted(event.data)
break
case 'user.subscription_changed':
await this.handleSubscriptionChanged(event.data)
break
default:
console.log(`Unhandled event type: ${event.type}`)
}
}
async handleResumeCreated(data) {
const { resume } = data
console.log(`New resume created: ${resume.id}`)
// Sync to local database
await this.syncResumeToDatabase(resume)
// Send notification
await this.sendNotification({
type: 'resume_created',
message: `Resume "${resume.title}" has been created`,
userId: resume.user_id
})
}
async handleAnalysisCompleted(data) {
const { analysis } = data
console.log(`Analysis completed for resume: ${analysis.resume_id}`)
// Update analysis results
await this.updateAnalysisResults(analysis)
// Send improvement suggestions if score is low
if (analysis.overall_score < 70) {
await this.sendImprovementSuggestions(analysis)
}
}
async handleSubscriptionChanged(data) {
const { user, subscription } = data
console.log(
`User ${user.id} subscription changed from ${subscription.previous_type} to ${subscription.current_type}`
)
// Update user permissions
await this.updateUserPermissions(user.id, subscription.current_type)
// Send welcome email (if upgrade)
if (subscription.current_type === 'PREMIUM' && subscription.previous_type === 'FREE') {
await this.sendWelcomePremiumEmail(user.email)
}
}
verifySignature(payload, signature, timestamp) {
const crypto = require('crypto')
const currentTime = Math.floor(Date.now() / 1000)
const webhookTime = parseInt(timestamp)
if (Math.abs(currentTime - webhookTime) > 300) {
throw new Error('Webhook timestamp too old')
}
const expectedSignature = crypto
.createHmac('sha256', this.secret)
.update(timestamp + '.' + payload)
.digest('hex')
const actualSignature = signature.replace('sha256=', '')
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature, 'hex'), Buffer.from(actualSignature, 'hex'))) {
throw new Error('Invalid webhook signature')
}
}
}
// Usage example
const webhookHandler = new SmartCVWebhookHandler(process.env.WEBHOOK_SECRET)
app.post('/webhooks/smartcv', express.raw({ type: 'application/json' }), async (req, res) => {
try {
await webhookHandler.handleWebhook(req.body, req.headers['x-smartcv-signature'], req.headers['x-smartcv-timestamp'])
res.status(200).send('OK')
} catch (error) {
console.error('Webhook error:', error)
res.status(400).send('Error processing webhook')
}
})🔄 Retry Mechanism
Retry Strategy
SmartCV uses exponential backoff strategy for retries:
- Retry Attempts: Maximum 3 times
- Retry Intervals: 1s, 2s, 4s
- Timeout: 30 seconds timeout per request
Success Criteria
The following HTTP status codes are considered successful:
200- OK201- Created202- Accepted204- No Content
🧪 Testing Webhooks
Testing Tools
You can use the following tools to test webhooks:
- ngrok
- Create local tunnel
- Webhook.site - Online testing tool
- RequestBin - Request collector
Manual Trigger Test
// Send test webhook
async function sendTestWebhook(webhookId) {
const response = await fetch(`/api/webhooks/${webhookId}/test`, {
method: 'POST',
credentials: 'include'
})
return response.json()
}📊 Monitoring and Debugging
Webhook Logs
You can view webhook delivery logs:
async function getWebhookLogs(webhookId, limit = 50) {
const response = await fetch(`/api/webhooks/${webhookId}/logs?limit=${limit}`, {
credentials: 'include'
})
return response.json()
}Log Response Example
{
"success": true,
"data": [
{
"id": "delivery_123",
"event_id": "event_456",
"event_type": "resume.created",
"url": "https://yourapp.com/webhooks/smartcv",
"status_code": 200,
"response_time": 145,
"attempts": 1,
"delivered_at": "2024-12-30T10:00:00Z"
},
{
"id": "delivery_124",
"event_id": "event_457",
"event_type": "ai.analysis_completed",
"url": "https://yourapp.com/webhooks/smartcv",
"status_code": 500,
"response_time": 30000,
"attempts": 3,
"last_attempt_at": "2024-12-30T10:05:00Z",
"next_retry_at": null,
"error": "Connection timeout"
}
]
}❌ Troubleshooting
Common Issues
-
Signature Verification Failed
- Check if the secret is correct
- Ensure using the original request body to calculate the signature
- Check if the timestamp is within the valid range
-
Duplicate Events
- Implement idempotent handling
- Use event ID for deduplication
-
Connection Timeout
- Optimize endpoint response time
- Handle long-running operations asynchronously
Best Practices
- Quick Response: Respond within 30 seconds
- Idempotent Handling: The same event may be sent multiple times
- Error Handling: Return proper error status codes
- Logging: Log all webhook events for debugging
Next Steps
Browse complete code examples and best practices
Learn how to perform user authentication and authorization
Last updated: 12/30/2024 •Suggest improvements