DocuTray

Implementation Examples

Complete implementation examples of webhooks in different languages and frameworks

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