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"
}
Header Description 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:
Event Status Trigger Operator accepted ESTIMATE_PENDINGOperator submitted time estimate Estimate approved ACCEPTEDYou approved the estimate Estimate rejected PENDINGYou rejected the estimate Work started IN_PROGRESSOperator started working Proof submitted SUBMITTEDOperator submitted proof Auto-verified VERIFIEDAI Guardian approved Completed COMPLETEDTask finalized, escrow released Manual review needed MANUAL_REVIEWAI Guardian confidence too low Disputed DISPUTEDYou rejected the proof Cancelled CANCELLEDYou 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.