Skip to content

Callback Contract

The Dispatcher POSTs a JSON payload to the Controller when a task reaches a terminal state. This document defines the payload schema, authentication, and error handling.

Endpoint

POST /api/v1/tasks/<task-guid>/callback/

Authentication

Two layers, both checked before the payload is processed:

  1. Bearer token -- Authorization: Bearer <token>. The token is generated at dispatch time and stored as a SHA-256 hash on the task record. The callback view hashes the incoming token and compares.

  2. HMAC signature (optional) -- When CALLBACK_SIGNING_KEY is set, the Dispatcher must send X-Kohakku-Signature: sha256=<hex>. The signature is HMAC-SHA256(key, "<task-guid>:<raw-body>").

Both checks use constant-time comparison to prevent timing attacks.

Payload Schema

Field Type Required Description
status string yes Terminal status: completed, failed, timed_out, or cancelled.
exit_code integer or null no Process exit code. 0 = success. null if killed before exit.
result_key string no Object storage key for the result artifact. Max 500 chars.
result_metadata object no Arbitrary metadata (tokens used, duration, etc.).
error_message string no Human-readable error. Max 5000 chars.
error string no Alias for error_message (backward compat). Max 5000 chars.
completed_at string (ISO 8601) no When the task finished on the Dispatcher side.
task_id string no Task UUID echoed back. Informational only.
log_stream string no Log stream reference or URL. Max 1000 chars.

Additional properties are not allowed. Unknown fields will be rejected.

Example -- successful completion

{
  "status": "completed",
  "exit_code": 0,
  "result_key": "results/550e8400-e29b-41d4-a716-446655440000/output.json",
  "result_metadata": {
    "tokens_used": 12450,
    "duration_seconds": 87
  }
}

Example -- failure

{
  "status": "failed",
  "exit_code": 137,
  "error_message": "Container killed: OOM (memory limit 2Gi exceeded)"
}

Response

200 OK -- callback accepted, task record updated.

{"status": "ok", "task_guid": "<uuid>"}

400 Bad Request -- payload failed schema validation.

{
  "error": "Invalid callback payload.",
  "validation_errors": [
    "(root): 'status' is a required property"
  ]
}

403 Forbidden -- invalid token or HMAC signature.

404 Not Found -- task GUID does not exist.

429 Too Many Requests -- rate limit exceeded (100 requests/minute).

Rate Limiting

Callbacks are rate-limited to 100 requests per minute per IP. The CallbackThrottle class in tasks/api.py enforces this.

Schema Definition

The canonical JSON Schema lives in controller/tasks/callback_schema.py as CALLBACK_SCHEMA. It is validated server-side using jsonschema before any task state is mutated.