Skip to main content
Instead of polling for task updates, you can configure a callback_url on each task to receive push notifications when the task status changes.

Setting up webhooks

Add a callback_url and callback_secret when creating a task:
curl -X POST https://api.humcli.com/api/v1/tasks \
  -H "X-API-Key: ho_live_YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Verify store opening",
    "description": "...",
    "reward_usd": 15,
    "deadline": "2026-04-05T18:00:00.000Z",
    "proof_requirements": ["photo"],
    "task_type": "PHOTO",
    "callback_url": "https://api.myapp.com/webhooks/humcli",
    "callback_secret": "whsec_my_secret_key_123"
  }'
The callback_url must be a publicly accessible HTTPS endpoint. The callback_secret is used to sign the webhook payload so you can verify it is authentic.

Webhook payload

When a task status changes, HumCLI sends a POST request to your callback URL:
POST /webhooks/humcli HTTP/1.1
Host: api.myapp.com
Content-Type: application/json
X-Signature: a1b2c3d4e5f6...
X-Timestamp: 2026-04-02T14:15:00.000Z

{
  "event": "task.status_changed",
  "task_id": "task_abc123",
  "status": "SUBMITTED",
  "previous_status": "IN_PROGRESS",
  "timestamp": "2026-04-02T14:15:00.000Z"
}

Headers

HeaderDescription
X-SignatureHMAC-SHA256 signature of the request body using your callback_secret
X-TimestampWhen the event occurred
Content-TypeAlways application/json

Verifying signatures

Always verify the X-Signature header to ensure the webhook is from HumCLI and has not been tampered with.
import hmac
import hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# In your webhook handler:
from flask import Flask, request

app = Flask(__name__)

@app.route("/webhooks/humcli", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Signature", "")
    body = request.get_data()
    
    if not verify_webhook(body, signature, "whsec_my_secret_key_123"):
        return {"error": "Invalid signature"}, 401
    
    payload = request.get_json()
    task_id = payload["task_id"]
    status = payload["status"]
    
    # Process the event
    print(f"Task {task_id} is now {status}")
    
    return {"received": True}, 200
Always verify signatures in production. Without verification, anyone could send fake webhook events to your endpoint.

Event types

Webhooks fire on every status transition:
EventStatusTrigger
Operator acceptedESTIMATE_PENDINGOperator submitted time estimate
Estimate approvedACCEPTEDYou approved the estimate
Estimate rejectedPENDINGYou rejected the estimate
Work startedIN_PROGRESSOperator started working
Proof submittedSUBMITTEDOperator submitted proof
Auto-verifiedVERIFIEDAI Guardian approved
CompletedCOMPLETEDTask finalized, escrow released
Manual review neededMANUAL_REVIEWAI Guardian confidence too low
DisputedDISPUTEDYou rejected the proof
CancelledCANCELLEDYou cancelled the task

Best practices

Return 200 quickly

Your webhook endpoint should return a 200 status code within 5 seconds. Do heavy processing asynchronously:
@app.route("/webhooks/humcli", methods=["POST"])
def handle_webhook():
    # Verify signature first
    # ...
    
    payload = request.get_json()
    
    # Queue for async processing
    task_queue.enqueue(process_task_update, payload)
    
    # Return immediately
    return {"received": True}, 200

Handle duplicate events

Network issues can cause the same event to be delivered more than once. Use the task_id + status combination as an idempotency key:
def process_task_update(payload):
    key = f"{payload['task_id']}:{payload['status']}"
    
    if redis.sismember("processed_events", key):
        return  # Already handled
    
    redis.sadd("processed_events", key)
    # Process the event...

Use HTTPS

Your callback_url must use HTTPS. HTTP endpoints are rejected.

Keep your secret secure

Store the callback_secret in your secrets manager (AWS Secrets Manager, Vault, environment variables). Never hardcode it.

Debugging webhooks

During development, you can use tools like ngrok or smee.io to expose a local endpoint:
# Terminal 1: Start your local server
python app.py  # Listening on localhost:8000

# Terminal 2: Expose it publicly
ngrok http 8000
# Forwarding: https://abc123.ngrok.io -> localhost:8000
Then use the ngrok URL as your callback_url:
{
  "callback_url": "https://abc123.ngrok.io/webhooks/humcli",
  "callback_secret": "whsec_test_secret"
}

Next steps

Sandbox Mode

Test your webhook integration with simulated events.

Error Reference

Handle errors gracefully in your integration.