Skip to main content
Webhooks push results to your server when a run finishes—no polling required. You provide a URL, we POST the result when it’s ready.
For async basics (fire-and-forget, polling with client.wait()), see Runs.

1. Add a Callback URL

Pass callbackUrl in the output field when creating a run:
curl -X POST https://api.subconscious.dev/v1/runs \
  -H "Authorization: Bearer $SUBCONSCIOUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "engine": "tim-gpt",
    "input": {
      "instructions": "Generate a detailed report"
    },
    "output": {
      "callbackUrl": "https://your-server.com/webhooks/subconscious"
    }
  }'
This returns immediately with a runId. When the run completes, we’ll POST the result to your callback URL.

2. Build a Webhook Handler

Create an endpoint to receive the webhook POST:
from fastapi import FastAPI, Request
import json

app = FastAPI()

@app.post("/webhooks/subconscious")
async def handle_webhook(request: Request):
    payload = await request.json()

    run_id = payload.get("runId")
    status = payload.get("status")

    if status == "succeeded":
        # Extract the answer from the result
        content = payload["result"]["choices"][0]["message"]["content"]
        parsed = json.loads(content)
        answer = parsed.get("answer", "")
        print(f"Run {run_id} completed: {answer[:100]}...")
        # Save to database, trigger next step, etc.
    elif status == "failed":
        print(f"Run {run_id} failed: {payload.get('error')}")

    return {"received": True}

# Run with: uvicorn server:app --host 0.0.0.0 --port 8000
Your endpoint must be publicly accessible. For local development, use ngrok or similar.

3. Webhook Payload

When a run finishes, we POST this JSON to your URL:
{
  "jobId": "9bb85845-9b20-4bb0-96dc-686a0aa3dcfe",
  "runId": "1a45adf1-ad50-4452-a2c7-150b5bcd215c",
  "orgId": "6d3c89bf-2665-45f8-87aa-c925979151c9",
  "status": "succeeded",
  "model": "tim-gpt",
  "engine": "tim-gpt",
  "result": {
    "choices": [
      {
        "message": {
          "role": "assistant",
          "content": "{\"reasoning\": [...], \"answer\": \"2 + 2 = 4.\"}"
        }
      }
    ],
    "usage": {
      "prompt_tokens": 1678,
      "completion_tokens": 83
    }
  },
  "error": null,
  "tokens": {
    "inputTokens": 1678,
    "outputTokens": 83,
    "costCents": 0
  },
  "createdAt": "2026-01-16T20:54:09.090Z",
  "startedAt": "2026-01-16T20:54:09.190Z",
  "completedAt": "2026-01-16T20:54:11.779Z"
}
FieldDescription
runIdThe run’s unique ID
jobIdInternal job ID
statusFinal status (succeeded, failed, timed_out, canceled)
resultThe completion result with choices array
errorError details (when failed, otherwise null)
tokensToken counts (inputTokens, outputTokens, costCents)
createdAtWhen the run was created
startedAtWhen processing began
completedAtWhen the run finished

Delivery Guarantees

Webhooks are delivered with:
  • Retries — Failed deliveries retry with exponential backoff
  • Timeouts — We wait up to 30 seconds for your 2xx response
  • Dead-letter queue — Exhausted retries are stored for inspection
  • Idempotency — Include a unique ID in your handler to prevent duplicates

Best Practices

  • Respond quickly — Return 2xx within 30 seconds, process async if needed
  • Be idempotent — You may receive the same webhook twice
  • Log payloads — Store raw payloads for debugging
  • Validate origin — Check request headers (signature verification coming soon)