Skip to main content
Skip table of contents

2. Consuming Webhooks

Consuming Webhooks

This scenario demonstrates how to receive and process webhook notifications from Circularo. Webhooks provide real-time updates about document events, allowing your application to respond immediately to changes in document status without the need for polling.

Key features:

  • Receive webhook notifications as HTTP POST requests

  • Verify webhook authenticity using HMAC signatures

  • Process different types of document events

  • Implement a simple webhook receiver in various programming languages

Understanding Webhook Payloads

When an event occurs in Circularo, a webhook notification is sent to your configured endpoint as an HTTP POST request with a JSON payload containing:

  • documentId: The unique identifier of the document that triggered the event

  • event: The type of event that occurred (e.g., documentCreated, documentSigned)

  • actor: The user who performed the action that triggered the event

  • timestamp: The date and time when the event occurred

Webhook Security

To ensure the authenticity of webhook notifications:

  • HMAC Verification: Webhook requests can include an `x-circularo-signature` header with a SHA-256 HMAC signature

  • Signature Calculation: The signature is calculated using the webhook's HMAC secret and the request body

  • Verification Process: Your application should calculate the expected signature and compare it with the received signature

  • Security Best Practices: Always verify signatures before processing webhook data to prevent spoofing

Never expose your HMAC secret in client-side code. Signature verification should always be performed on your server.

Prerequisites

Before consuming webhooks, you need:

  • A publicly accessible URL that can receive HTTP POST requests

  • A webhook configured in Circularo pointing to your URL

  • If using HMAC verification: The secret key configured when creating the webhook

Implementing a Webhook Receiver

To consume webhooks from Circularo, you need to implement an HTTP endpoint that can receive POST requests. Below are examples of webhook receivers in different programming languages.

Example Webhook Receiver Implementation

These examples show basic webhook receivers. In production, add proper error handling, logging, and business logic based on the event type.

Node.js (Express)

JAVASCRIPT
// Webhook receiver example using Express.js
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;
const HMAC_SECRET = "Gh1!f5dFs0dA"; // Secret configured when creating the webhook

// Use raw body parser to access the raw request body for signature verification
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf;
  }
}));

// Webhook endpoint
app.post('/webhook-endpoint', (req, res) => {
  try {
    // Step 1: Verify the webhook signature
    const signature = req.headers['x-circularo-signature'];
    const isValid = verifySignature(req.rawBody, signature, HMAC_SECRET);

    if (!isValid) {
      console.error('Invalid webhook signature');
      return res.status(401).send('Invalid signature');
    }

    // Step 2: Process the webhook data
    const webhookData = req.body;
    console.log('Webhook received:', webhookData);

    // Step 3: Handle different event types
    switch(webhookData.event) {
      case 'documentCreated':
        console.log(`New document created: ${webhookData.documentId}`);
        // Add your document creation handling logic here
        break;

      case 'documentSigned':
        console.log(`Document signed by ${webhookData.actor}: ${webhookData.documentId}`);
        // Add your document signing handling logic here
        break;

      case 'documentCompleted':
        console.log(`Document completed: ${webhookData.documentId}`);
        // Add your document completion handling logic here
        break;

      case 'signRejected':
        console.log(`Signing rejected for document: ${webhookData.documentId}`);
        // Add your rejection handling logic here
        break;

      // Other event types go here

      default:
        console.log(`Unhandled event type: ${webhookData.event}`);
    }

    // Step 4: Always respond with 200 OK quickly
    // This lets Circularo know the webhook was received
    res.status(200).send('Webhook processed successfully');

  } catch (error) {
    console.error('Error processing webhook:', error);
    res.status(500).send('Error processing webhook');
  }
});

// Function to verify webhook signature
function verifySignature(payload, signature, secret) {
  if (!signature || !secret) return false;

  const hmac = crypto.createHmac('sha256', secret);
  const calculatedSignature = hmac.update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(calculatedSignature, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

app.listen(PORT, () => {
  console.log(`Webhook receiver listening on port ${PORT}`);
});

Python (Flask)

PYTHON
# Webhook receiver example using Flask
from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
HMAC_SECRET = "Gh1!f5dFs0dA"  # Secret configured when creating the webhook

@app.route('/webhook-endpoint', methods=['POST'])
def webhook_handler():
    try:
        # Step 1: Get the signature from headers
        signature = request.headers.get('x-circularo-signature')

        # Step 2: Verify the signature
        if not verify_signature(request.data, signature, HMAC_SECRET):
            return jsonify({'error': 'Invalid signature'}), 401

        # Step 3: Process the webhook data
        webhook_data = request.json
        print(f"Webhook received: {webhook_data}")

        # Step 4: Handle different event types
        event_type = webhook_data.get('event')
        document_id = webhook_data.get('documentId')
        actor = webhook_data.get('actor')

        if event_type == 'documentCreated':
            print(f"New document created: {document_id}")
            # Add your document creation handling logic here

        elif event_type == 'documentSigned':
            print(f"Document signed by {actor}: {document_id}")
            # Add your document signing handling logic here

        elif event_type == 'documentCompleted':
            print(f"Document completed: {document_id}")
            # Add your document completion handling logic here

        elif event_type == 'signRejected':
            print(f"Signing rejected for document: {document_id}")
            # Add your rejection handling logic here

        # Other event types go here

        else:
            print(f"Unhandled event type: {event_type}")

        # Step 5: Always respond quickly with success
        return jsonify({'status': 'success'}), 200

    except Exception as e:
        print(f"Error processing webhook: {str(e)}")
        return jsonify({'error': str(e)}), 500

def verify_signature(payload, signature, secret):
    """Verify the HMAC signature of the webhook payload"""
    if not signature or not secret:
        return False

    # Calculate expected signature
    hmac_obj = hmac.new(
        key=secret.encode('utf-8'),
        msg=payload,
        digestmod=hashlib.sha256
    )
    calculated_signature = hmac_obj.hexdigest()

    # Compare signatures using constant-time comparison
    return hmac.compare_digest(calculated_signature, signature)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)

PHP

PHP
<?php
// Webhook receiver example in PHP

// Secret configured when creating the webhook
$hmacSecret = "Gh1!f5dFs0dA";

// Step 1: Get the raw request body
$rawBody = file_get_contents('php://input');
$webhookData = json_decode($rawBody, true);

// Step 2: Get the signature from headers
$signature = $_SERVER['HTTP_X_CIRCULARO_SIGNATURE'] ?? '';

// Step 3: Verify the signature
if (!verifySignature($rawBody, $signature, $hmacSecret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Step 4: Process the webhook data
error_log('Webhook received: ' . print_r($webhookData, true));

// Step 5: Handle different event types
$eventType = $webhookData['event'] ?? '';
$documentId = $webhookData['documentId'] ?? '';
$actor = $webhookData['actor'] ?? '';

switch ($eventType) {
    case 'documentCreated':
        error_log("New document created: {$documentId}");
        // Add your document creation handling logic here
        break;

    case 'documentSigned':
        error_log("Document signed by {$actor}: {$documentId}");
        // Add your document signing handling logic here
        break;

    case 'documentCompleted':
        error_log("Document completed: {$documentId}");
        // Add your document completion handling logic here
        break;

    case 'signRejected':
        error_log("Signing rejected for document: {$documentId}");
        // Add your rejection handling logic here
        break;

    // Other event types go here

    default:
        error_log("Unhandled event type: {$eventType}");
}

// Step 6: Always respond quickly with success
http_response_code(200);
echo json_encode(['status' => 'success']);

/**
 * Verify the HMAC signature of the webhook payload
 */
function verifySignature($payload, $signature, $secret) {
    if (empty($signature) || empty($secret)) {
        return false;
    }

    // Calculate expected signature
    $calculatedSignature = hash_hmac('sha256', $payload, $secret);

    // Compare signatures using constant-time comparison
    return hash_equals($calculatedSignature, $signature);
}

Best Practices for Webhook Consumption

  • Respond Quickly: Always respond to webhook requests promptly to prevent timeouts

  • Verify Signatures: Always verify webhook signatures to ensure authenticity (when HMAC secret is configured)

  • Process Asynchronously: For time-consuming operations, acknowledge the webhook immediately and process the data in the background

  • Implement Retry Logic: Be prepared to handle temporary failures in your webhook processing

Common Webhook Events

  • documentCreated: A new document has been created in the system

  • signRequest: A document has been shared with recipients for signing

  • documentSigned: A document has been signed by a user

  • documentCompleted: All required signatures have been collected for a document

  • signRejected: A recipient has rejected the document signing request

  • documentDeleted: A document has been deleted from the system

Troubleshooting Webhooks

  • Webhook Not Received: Verify your endpoint is publicly accessible and the webhook is correctly configured

  • Signature Verification Fails: Ensure you're using the correct HMAC secret and properly handling the raw request body

  • Intermittent Failures: Implement proper logging to identify patterns in webhook failures

  • Testing Webhooks: Use services like webhook.site for initial testing before implementing your own endpoint

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.