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)
// 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)
# 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
// 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