Voice Notes Webhooks Guide
Receive voice note transcriptions automatically in your backend. VocaFuse sends an HTTP POST request with the transcription text when processing completes. This guide covers webhook setup, HMAC signature verification, retry logic, and Python implementation examples.
Overview
VocaFuse uses webhooks to notify your application when voice notes are transcribed or when errors occur. Webhooks are HTTP POST requests sent to your configured endpoint.
Event Types
| Event | Description | 
|---|---|
voicenote.transcribed | Voice note successfully transcribed | 
voicenote.failed | Voice note processing failed | 
Webhook Payload
voicenote.transcribed
{
  "event": "voicenote.transcribed",
  "timestamp": 1703001600,
  "voicenote": {
    "id": "rec_1234567890",
    "identity": "user_12345",
    "status": "completed",
    "duration": 45.2,
    "created_at": 1703001500,
    "completed_at": 1703001600,
    "transcription": {
      "text": "This is the transcribed text",
      "confidence": 0.985,
      "language": "en"
    }
  }
}
voicenote.failed
{
  "event": "voicenote.failed",
  "timestamp": 1703001600,
  "voicenote": {
    "id": "rec_1234567890",
    "identity": "user_12345",
    "status": "failed",
    "created_at": 1703001500
  },
  "error": {
    "code": "TRANSCRIPTION_FAILED",
    "message": "Audio quality too low for transcription"
  }
}
Setup
1. Create Endpoint
Create an endpoint in your application to receive webhooks:
- Python
 - Node.js
 
from flask import Flask, request, jsonify
from vocafuse import RequestValidator
import os
app = Flask(__name__)
validator = RequestValidator(os.environ['VOCAFUSE_WEBHOOK_SECRET'])
@app.route('/api/webhooks/vocafuse', methods=['POST'])
def handle_webhook():
    # Get raw payload and signature
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-VocaFuse-Signature')
    
    # Verify signature
    if not validator.validate(payload, signature):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Parse event
    data = request.get_json()
    event = data['event']
    
    if event == 'voicenote.transcribed':
        handle_completed(data['voicenote'])
    elif event == 'voicenote.failed':
        handle_failed(data['voicenote'], data['error'])
    
    return jsonify({'status': 'received'}), 200
def handle_completed(voicenote):
    print(f"Transcription: {voicenote['transcription']['text']}")
    # Save to database, notify user, etc.
def handle_failed(voicenote, error):
    print(f"Failed: {error['message']}")
    # Log error, notify user, etc.
const express = require('express');
const { RequestValidator } = require('@vocafuse/backend-sdk');
const app = express();
app.use(express.json());
const validator = new RequestValidator(process.env.VOCAFUSE_WEBHOOK_SECRET);
app.post('/api/webhooks/vocafuse', (req, res) => {
  // Get payload and signature
  const payload = JSON.stringify(req.body);
  const signature = req.headers['x-vocafuse-signature'];
  
  // Verify signature
  if (!validator.validate(payload, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Handle event
  const { event, voicenote } = req.body;
  
  if (event === 'voicenote.transcribed') {
    handleCompleted(voicenote);
  } else if (event === 'voicenote.failed') {
    handleFailed(voicenote, req.body.error);
  }
  
  res.json({ status: 'received' });
});
function handleCompleted(voicenote) {
  console.log(`Transcription: ${voicenote.transcription.text}`);
  // Save to database, notify user, etc.
}
function handleFailed(voicenote, error) {
  console.log(`Failed: ${error.message}`);
  // Log error, notify user, etc.
}
2. Register Webhook
Register your endpoint with VocaFuse:
- Python
 - Node.js
 - cURL
 
from vocafuse import VocaFuse
import os
client = VocaFuse(
    api_key=os.environ['VOCAFUSE_API_KEY'],
    api_secret=os.environ['VOCAFUSE_API_SECRET']
)
webhook = client.webhooks.create(
    url='https://your-domain.com/api/webhooks/vocafuse',
    events=['voicenote.transcribed', 'voicenote.failed'],
    secret='your_webhook_secret_here'
)
print(f"Webhook ID: {webhook['data']['id']}")
const { VocaFuse } = require('@vocafuse/backend-sdk');
const client = new VocaFuse({
  apiKey: process.env.VOCAFUSE_API_KEY,
  apiSecret: process.env.VOCAFUSE_API_SECRET
});
const webhook = await client.webhooks.create({
  url: 'https://your-domain.com/api/webhooks/vocafuse',
  events: ['voicenote.transcribed', 'voicenote.failed'],
  secret: 'your_webhook_secret_here'
});
console.log(`Webhook ID: ${webhook.data.id}`);
curl -X POST https://api.vocafuse.com/v1/webhooks \
  -H "X-VocaFuse-API-Key: sk_live_..." \
  -H "X-VocaFuse-API-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/api/webhooks/vocafuse",
    "events": ["voicenote.transcribed", "voicenote.failed"],
    "secret": "your_webhook_secret_here"
  }'
Security
Signature Verification
Always verify webhook signatures before processing events. This prevents attackers from sending fake webhooks to your endpoint.
VocaFuse signs all webhooks with HMAC-SHA256. Use the SDK's RequestValidator to verify signatures:
- Python
 - Node.js
 
from vocafuse import RequestValidator
validator = RequestValidator('your_webhook_secret')
is_valid = validator.validate(payload, signature)
const { RequestValidator } = require('@vocafuse/backend-sdk');
const validator = new RequestValidator('your_webhook_secret');
const isValid = validator.validate(payload, signature);
Best Practices
- Use HTTPS - Only accept webhooks over HTTPS
 - Verify signatures - Always validate the 
X-VocaFuse-Signatureheader - Return quickly - Respond with 200 within 5 seconds
 - Process async - Queue long-running tasks for background processing
 - Handle retries - Implement idempotency using the voice note ID
 - Log events - Keep audit logs of all webhook deliveries
 
Testing
Local Development
Use ngrok to test webhooks locally:
# Start your server
python app.py
# In another terminal
ngrok http 5000
# Use the ngrok URL for webhook registration
# Example: https://abc123.ngrok.io/api/webhooks/vocafuse
Manual Testing
Send a test webhook from the VocaFuse dashboard:
- Python
 
# Or use the API
client.webhooks('webhook_123').test()
Monitoring
Webhook Logs
View webhook delivery logs in the dashboard:
- Delivery attempts
 - Response status codes
 - Response times
 - Retry attempts
 
Failed Deliveries
VocaFuse will retry failed webhooks with exponential backoff:
| Attempt | Delay | 
|---|---|
| 1 | 0s | 
| 2 | 30s | 
| 3 | 5min | 
| 4 | 30min | 
| 5 | 2h | 
After 5 failed attempts, the webhook will be marked as failed and no more retries will be attempted.
Error Handling
- Python
 
@app.route('/api/webhooks/vocafuse', methods=['POST'])
def handle_webhook():
    try:
        # Verify signature
        if not verify_signature():
            return jsonify({'error': 'Invalid signature'}), 401
        
        data = request.get_json()
        
        # Process event
        process_event(data)
        
        # Always return 200
        return jsonify({'status': 'received'}), 200
        
    except Exception as e:
        # Log error but still return 200 to prevent retries
        logger.error(f'Webhook processing error: {e}')
        return jsonify({'status': 'error', 'message': str(e)}), 200
Common Issues
Issue: Signature validation fails
Cause: Incorrect secret or payload modification
Solution:
- Python
 
# Ensure you're using the raw payload, not parsed JSON
payload = request.get_data(as_text=True)  # ✅ Correct
payload = json.dumps(request.get_json())  # ❌ Wrong
Issue: Timeouts
Cause: Slow processing in webhook handler
Solution:
- Python
 
# Queue for background processing
@app.route('/api/webhooks/vocafuse', methods=['POST'])
def handle_webhook():
    data = request.get_json()
    
    # Queue the task
    queue.enqueue(process_webhook, data)
    
    # Return immediately
    return jsonify({'status': 'received'}), 200
Issue: Duplicate events
Cause: Webhook retries
Solution:
- Python
 
# Use voice note ID for idempotency
processed_ids = set()
def process_event(data):
    voicenote_id = data['voicenote']['id']
    
    if voicenote_id in processed_ids:
        return  # Already processed
    
    # Process event...
    processed_ids.add(voicenote_id)