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
بحاجة الى مساعدة؟اتصل بنا للحصول على الدعم
آخر تحديث:٣٠/١٢/٢٠٢٤ •أقترح التحسينات