Implementation Examples
Complete implementation examples of webhooks in different languages and frameworks
Implementation Examples
This page provides complete examples of how to implement Docutray webhooks in different languages and frameworks.
Node.js/Express
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/docutray', (req, res) => {
const signature = req.headers['x-docutray-signature'];
const eventType = req.headers['x-docutray-event'];
const payload = req.body;
// Verify signature
const secret = process.env.DOCUTRAY_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (`sha256=${expectedSignature}` !== signature) {
return res.status(401).send('Signature verification failed');
}
const data = JSON.parse(payload);
// Process based on event type
switch (eventType) {
// Conversion Events
case 'CONVERSION_STARTED':
console.log(`Conversion started: ${data.conversion_id}`);
break;
case 'CONVERSION_COMPLETED':
console.log(`Conversion completed: ${data.conversion_id}`);
console.log('Extracted data:', data.data);
break;
case 'CONVERSION_FAILED':
console.log(`Conversion failed: ${data.conversion_id}`);
console.log('Error:', data.error);
break;
// Identification Events
case 'IDENTIFICATION_STARTED':
console.log(`Identification started: ${data.identification_id}`);
break;
case 'IDENTIFICATION_COMPLETED':
console.log(`Identification completed: ${data.identification_id}`);
console.log('Identified type:', data.document_type);
break;
case 'IDENTIFICATION_FAILED':
console.log(`Identification failed: ${data.identification_id}`);
console.log('Error:', data.error);
break;
// Steps Events
case 'STEP_STARTED':
console.log(`Step started: ${data.step_name} (${data.step_execution_id})`);
break;
case 'STEP_COMPLETED':
console.log(`Step completed: ${data.step_name} (${data.step_execution_id})`);
if (data.data) console.log('Processed data:', data.data);
if (data.validation) console.log('Validation:', data.validation);
break;
case 'STEP_FAILED':
console.log(`Step failed: ${data.step_name} (${data.step_execution_id})`);
console.log('Error:', data.error);
break;
}
res.status(200).send('OK');
});
app.listen(3000);Python/Flask
import hmac
import hashlib
import json
import os
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/docutray', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Docutray-Signature')
event_type = request.headers.get('X-Docutray-Event')
payload = request.get_data()
# Verify signature
secret = os.environ['DOCUTRAY_WEBHOOK_SECRET'].encode()
expected_signature = hmac.new(
secret,
payload,
hashlib.sha256
).hexdigest()
if f'sha256={expected_signature}' != signature:
return 'Signature verification failed', 401
data = json.loads(payload)
# Process based on event type
# Conversion Events
if event_type == 'CONVERSION_STARTED':
print(f"Conversion started: {data['conversion_id']}")
elif event_type == 'CONVERSION_COMPLETED':
print(f"Conversion completed: {data['conversion_id']}")
print(f"Extracted data: {data['data']}")
elif event_type == 'CONVERSION_FAILED':
print(f"Conversion failed: {data['conversion_id']}")
print(f"Error: {data['error']}")
# Identification Events
elif event_type == 'IDENTIFICATION_STARTED':
print(f"Identification started: {data['identification_id']}")
elif event_type == 'IDENTIFICATION_COMPLETED':
print(f"Identification completed: {data['identification_id']}")
print(f"Identified type: {data['document_type']}")
elif event_type == 'IDENTIFICATION_FAILED':
print(f"Identification failed: {data['identification_id']}")
print(f"Error: {data['error']}")
# Steps Events
elif event_type == 'STEP_STARTED':
print(f"Step started: {data['step_name']} ({data['step_execution_id']})")
elif event_type == 'STEP_COMPLETED':
print(f"Step completed: {data['step_name']} ({data['step_execution_id']})")
if 'data' in data:
print(f"Processed data: {data['data']}")
if 'validation' in data:
print(f"Validation: {data['validation']}")
elif event_type == 'STEP_FAILED':
print(f"Step failed: {data['step_name']} ({data['step_execution_id']})")
print(f"Error: {data['error']}")
return 'OK', 200
if __name__ == '__main__':
app.run(port=3000)Python/FastAPI
import hmac
import hashlib
import os
from fastapi import FastAPI, Request, Header, HTTPException
app = FastAPI()
@app.post("/webhooks/docutray")
async def handle_webhook(
request: Request,
x_docutray_signature: str = Header(...),
x_docutray_event: str = Header(...)
):
payload = await request.body()
# Verify signature
secret = os.environ['DOCUTRAY_WEBHOOK_SECRET'].encode()
expected_signature = hmac.new(
secret,
payload,
hashlib.sha256
).hexdigest()
if f'sha256={expected_signature}' != x_docutray_signature:
raise HTTPException(status_code=401, detail="Signature verification failed")
data = await request.json()
# Process based on event type
if x_docutray_event == 'CONVERSION_STARTED':
print(f"Conversion started: {data['conversion_id']}")
elif x_docutray_event == 'CONVERSION_COMPLETED':
print(f"Conversion completed: {data['conversion_id']}")
print(f"Extracted data: {data['data']}")
elif x_docutray_event == 'CONVERSION_FAILED':
print(f"Conversion failed: {data['conversion_id']}")
# ... other events
return {"status": "ok"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=3000)PHP
<?php
$signature = $_SERVER['HTTP_X_DOCUTRAY_SIGNATURE'];
$eventType = $_SERVER['HTTP_X_DOCUTRAY_EVENT'];
$payload = file_get_contents('php://input');
// Verify signature
$secret = getenv('DOCUTRAY_WEBHOOK_SECRET');
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if ($signature !== $expectedSignature) {
http_response_code(401);
echo 'Signature verification failed';
exit;
}
$data = json_decode($payload, true);
// Process based on event type
switch ($eventType) {
case 'CONVERSION_STARTED':
error_log("Conversion started: " . $data['conversion_id']);
break;
case 'CONVERSION_COMPLETED':
error_log("Conversion completed: " . $data['conversion_id']);
error_log("Extracted data: " . json_encode($data['data']));
break;
case 'CONVERSION_FAILED':
error_log("Conversion failed: " . $data['conversion_id']);
error_log("Error: " . $data['error']);
break;
// ... other events
}
http_response_code(200);
echo 'OK';Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
type WebhookData map[string]interface{}
func verifySignature(payload []byte, signature string, secret string) bool {
h := hmac.New(sha256.New, []byte(secret))
h.Write(payload)
expectedSignature := "sha256=" + hex.EncodeToString(h.Sum(nil))
return expectedSignature == signature
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Docutray-Signature")
eventType := r.Header.Get("X-Docutray-Event")
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
// Verify signature
secret := os.Getenv("DOCUTRAY_WEBHOOK_SECRET")
if !verifySignature(payload, signature, secret) {
http.Error(w, "Signature verification failed", http.StatusUnauthorized)
return
}
var data WebhookData
if err := json.Unmarshal(payload, &data); err != nil {
http.Error(w, "Error parsing JSON", http.StatusBadRequest)
return
}
// Process based on event type
switch eventType {
case "CONVERSION_STARTED":
log.Printf("Conversion started: %v", data["conversion_id"])
case "CONVERSION_COMPLETED":
log.Printf("Conversion completed: %v", data["conversion_id"])
log.Printf("Extracted data: %v", data["data"])
case "CONVERSION_FAILED":
log.Printf("Conversion failed: %v", data["conversion_id"])
log.Printf("Error: %v", data["error"])
// ... other events
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
}
func main() {
http.HandleFunc("/webhooks/docutray", handleWebhook)
log.Println("Server started on :3000")
log.Fatal(http.ListenAndServe(":3000", nil))
}Ruby/Sinatra
require 'sinatra'
require 'json'
require 'openssl'
post '/webhooks/docutray' do
signature = request.env['HTTP_X_DOCUTRAY_SIGNATURE']
event_type = request.env['HTTP_X_DOCUTRAY_EVENT']
payload = request.body.read
# Verify signature
secret = ENV['DOCUTRAY_WEBHOOK_SECRET']
expected_signature = 'sha256=' + OpenSSL::HMAC.hexdigest('sha256', secret, payload)
if signature != expected_signature
halt 401, 'Signature verification failed'
end
data = JSON.parse(payload)
# Process based on event type
case event_type
when 'CONVERSION_STARTED'
puts "Conversion started: #{data['conversion_id']}"
when 'CONVERSION_COMPLETED'
puts "Conversion completed: #{data['conversion_id']}"
puts "Extracted data: #{data['data']}"
when 'CONVERSION_FAILED'
puts "Conversion failed: #{data['conversion_id']}"
puts "Error: #{data['error']}"
# ... other events
end
status 200
body 'OK'
endImplementation recommendations
Asynchronous processing
For webhooks that require heavy processing, consider using a task queue:
// Example with Bull (Redis)
const Queue = require('bull');
const webhookQueue = new Queue('webhook-processing');
app.post('/webhooks/docutray', async (req, res) => {
// Verify signature first
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Add to queue for asynchronous processing
await webhookQueue.add({
eventType: req.headers['x-docutray-event'],
data: JSON.parse(req.body)
});
// Respond immediately
res.status(200).send('OK');
});
// Process in background
webhookQueue.process(async (job) => {
const { eventType, data } = job.data;
// Heavy processing here
});Error handling and retries
app.post('/webhooks/docutray', async (req, res) => {
try {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
await processWebhook(req.body);
// Respond successfully
res.status(200).send('OK');
} catch (error) {
console.error('Error processing webhook:', error);
// Return 500 error so Docutray retries
res.status(500).send('Internal server error');
}
});Logging and debugging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'webhooks.log' })
]
});
app.post('/webhooks/docutray', (req, res) => {
const requestId = req.headers['x-docutray-request-id'];
const eventType = req.headers['x-docutray-event'];
logger.info('Webhook received', {
requestId,
eventType,
timestamp: new Date().toISOString()
});
// Process webhook...
logger.info('Webhook processed', {
requestId,
eventType,
duration: Date.now() - startTime
});
res.status(200).send('OK');
});