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'
end

Implementation 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');
});

On this page