Documentation

Webhooks API

SmartCV Webhooks API documentation, learn how to receive real-time event notifications

Updated: 12/30/2024

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

Event TypeDescription
resume.createdResume created
resume.updatedResume updated
resume.deletedResume deleted
resume.sharedResume shared
resume.exportedResume exported
Event TypeDescription
user.registeredUser registered
user.subscription_changedSubscription status changed
user.profile_updatedUser profile updated
Event TypeDescription
ai.analysis_completedAI analysis completed
ai.content_generatedAI 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 - OK
  • 201 - Created
  • 202 - Accepted
  • 204 - No Content

🧪 Testing Webhooks

Testing Tools

You can use the following tools to test webhooks:

  1. ngrok
  • Create local tunnel
  1. Webhook.site - Online testing tool
  2. 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

  1. 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
  2. Duplicate Events

    • Implement idempotent handling
    • Use event ID for deduplication
  3. 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

💡 Example Code

Browse complete code examples and best practices

🔐 Authentication System

Learn how to perform user authentication and authorization