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

Webhooks API - SmartCV