Example: Handling HITL Task Callbacks

Learn how to build a listener endpoint to asynchronously receive and process notifications for Human-in-the-Loop (HITL) task events from KirokuForms.

Asynchronous HITL with Callbacks

When your AI system creates a Human-in-the-Loop task using the KirokuForms MCP API or Python SDK, you can provide a callbackUrl. When the status of this task changes (e.g., a human reviewer completes it), KirokuForms will send an HTTP POST request (a callback, which is a type of webhook) to your specified URL.

This event-driven approach is highly efficient as it avoids the need for your application to continuously poll KirokuForms for task status updates, especially useful for tasks that might take a significant amount of time for human review.

General Webhook Concepts

This page focuses on a specific example for HITL task callbacks. For a comprehensive understanding of general webhook principles, managing form-level webhooks (e.g., for all new submissions to a specific form), detailed signature verification steps, and troubleshooting, please refer to our main Webhooks Documentation.

1. Setting the Callback URL

You specify your listener's URL in the callbackUrl parameter when creating a HITL task:

Using the Python SDK:

# When creating a HITL task with the Python SDK:
hitl_task = kiroku_hitl_client.create_task(
    title="Review Document Classification",
    # ... other task parameters (fields, etc.) ...
    callback_url="https://your-server.com/api/kirokuforms-hitl-callback" # Your listener endpoint
)

Using the MCP API Directly:

# When creating a HITL task directly via MCP API:
# POST https://www.kirokuforms.com/api/mcp/tools/request-human-review
# Payload:
{
  "taskTitle": "Verify Extracted Entities",
  "inputData": { "text_to_review": "Some text..." },
  "fields": [
    { "name": "is_correct", "type": "radio", "label": "Correct?", "options": ["Yes", "No"] },
    { "name": "comments", "type": "textarea", "label": "Comments" }
  ],
  "callbackUrl": "https://your-server.com/api/kirokuforms-hitl-callback"
}

Ensure https://your-server.com/api/kirokuforms-hitl-callback is replaced with your actual, publicly accessible endpoint URL.

2. Expected HITL Callback Payload

When a subscribed HITL task event occurs (e.g., hitl.task.completed), KirokuForms sends a JSON payload to your callbackUrl. Here's an example structure:

{
  "eventType": "hitl.task.completed", // Could also be hitl.task.updated, etc.
  "taskId": "task_abc123xyz789",      // The KirokuForms Task ID 
  "timestamp": "2025-07-15T11:45:00Z",
  "data": {
    "status": "completed",
    "formData": {
      // Key-value pairs of the data submitted by the human reviewer
      "is_correct": "Yes",
      "comments": "The entities look accurate."
    }
    // Potentially other task-related metadata
  }
}

Your listener should parse this payload to extract the eventType, taskId, and the submitted formData within the data object.

3. Example: Simple HITL Callback Listener (Python/Flask)

Below is a basic example of a Flask application in Python that can receive and process these HITL task callbacks. Remember to implement robust signature verification in a production environment as detailed in our main Webhooks Documentation.

from flask import Flask, request, jsonify
import hmac
import hashlib
import os
import json

app = Flask(__name__)

# Store your secret securely, e.g., in an environment variable
# This secret should match what KirokuForms uses to sign this specific callback/webhook
KIROKU_HITL_CALLBACK_SECRET = os.environ.get("KIROKU_HITL_SECRET")

def verify_signature(payload_body, signature_header):
    if not KIROKU_HITL_CALLBACK_SECRET:
        print("Webhook secret not configured. Skipping signature verification (NOT FOR PRODUCTION!).")
        return True # Insecure, for local testing only if secret is not set

    if not signature_header:
        print("Signature header missing.")
        return False

    # Assuming KirokuForms-Signature format: t=timestamp,v1=signature
    # This is an example; adapt to KirokuForms' actual HITL callback signature scheme.
    try:
        timestamp_str, sig_v1_str = signature_header.split(',')
        timestamp = timestamp_str.split('=')[1]
        provided_signature = sig_v1_str.split('=')[1]

        # Optional: Check timestamp staleness (e.g., within 5 minutes)
        # ...

        string_to_sign = f"{timestamp}.{payload_body}" # Note: payload_body must be raw string
        
        expected_signature = hmac.new(
            KIROKU_HITL_CALLBACK_SECRET.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()

        return hmac.compare_digest(expected_signature, provided_signature)
    except Exception as e:
        print(f"Error verifying signature: {e}")
        return False

@app.route('/api/kirokuforms-hitl-callback', methods=['POST'])
def kiroku_hitl_callback():
    raw_body = request.get_data(as_text=True) # Get raw body for signature verification
    signature = request.headers.get('KirokuForms-Signature') # Or actual header name

    # ** ALWAYS VERIFY THE SIGNATURE IN PRODUCTION **
    # if not verify_signature(raw_body, signature):
    #   print("Webhook signature verification failed!")
    #   return jsonify({"error": "Invalid signature"}), 400

    try:
        payload = json.loads(raw_body) # Now parse JSON
    except json.JSONDecodeError:
        print("Invalid JSON payload received.")
        return jsonify({"error": "Invalid JSON"}), 400
        
    print(f"Received HITL Callback - Event Type: {payload.get('eventType')}, Task ID: {payload.get('taskId')}")

    if payload.get('eventType') == 'hitl.task.completed':
        task_id = payload.get('taskId')
        form_data = payload.get('data', {}).get('formData', {})
        print(f"HITL Task {task_id} completed. Data: {form_data}")
        
        # Add your processing logic here:
        # - Update your database
        # - Resume a paused workflow (e.g., a LangGraph agent)
        # - Send further notifications, etc.
    
    # Acknowledge receipt quickly
    return jsonify({"status": "success", "message": "Callback received"}), 200

if __name__ == '__main__':
    # For local testing, you might use ngrok to expose this endpoint
    # app.run(port=5001, debug=True)
    pass
Security First!

The signature verification part in the example above is conceptual and commented out. **Always implement and enforce signature verification for all incoming webhooks in your production environment.** This ensures the data originates from KirokuForms and hasn't been tampered with. Refer to the full Webhooks Security Guide for detailed instructions.

4. Expanding Your HITL Workflow

Once your listener receives a callback for a completed HITL task, you can:

  • Store the human-provided formData in your database.
  • Use the feedback to fine-tune your AI models.
  • If using LangGraph with checkpoints, this callback might trigger an external process to resume the graph execution with the human input.
  • Notify relevant stakeholders or kick off subsequent business processes.

5. Other Notification & Integration Options

Beyond programmatic HITL task callbacks and general form webhooks, KirokuForms offers other ways to stay informed:

  • Email Notifications for Form Submissions: For standard forms, you can easily configure email notifications to be sent to specified addresses upon new submissions. This is typically set up directly in the form's settings via the KirokuForms web UI dashboard.
  • Upcoming Integrations: We are always working to expand our integration capabilities. Native integrations with popular services (e.g., Slack, Zapier, CRMs) are on our roadmap.

Have a specific integration or notification type you need? We'd love to hear about it! Request an Integration or share your feedback.

Further Reading