Request
The HTTPS URL to deliver events to. Must use https:// in production. Subject to SSRF validation — private IPs, cloud metadata endpoints, and non-HTTP schemes are blocked.
Array of event types to subscribe to. Use "*" for all events, or specific types like "completion.created".Valid types: completion.created, job.completed, job.failed, workflow.completed, workflow.failed, skill.installed, skill.uninstalled, skill.submitted, skill.approved, skill.rejected, skill.suspended, connector.created, connector.status_changed, credits.low, key.created, key.revoked, *
Optional custom signing secret (16–256 chars). If omitted, a cryptographically random 32-byte hex secret is generated.
Optional description for the webhook (max 256 chars).
Response
The webhook’s unique UUID.
The registered endpoint URL.
The HMAC-SHA256 signing secret. Shown once — save it immediately. Used to verify webhook signatures on your server.
Authentication
Requires an org-scoped API key with completions scope.
Authorization: Bearer theo_sk_...
Example
curl -X POST https://hitheo.ai/api/v1/webhooks \
-H "Authorization: Bearer $THEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/theo",
"event_types": ["completion.created", "job.completed"]
}'
Response
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://your-server.com/webhooks/theo",
"event_types": ["completion.created", "job.completed"],
"enabled": true,
"signing_secret": "8f3a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1",
"created_at": "2026-04-15T12:00:00.000Z",
"_note": "Save the signing_secret — it will not be shown again."
}
Limits
- A per-organization cap on active webhooks applies. If you need more, contact support.
- URL must pass SSRF validation (no private IPs, no cloud metadata endpoints).
- HTTPS required in production.
Signature Verification
Every delivery includes three headers for verification:
Theo-Webhook-Signature: v1=<hex>
Theo-Webhook-Timestamp: <unix_seconds>
Theo-Webhook-Id: <event_id>
Verify by computing HMAC-SHA256(signing_secret, timestamp + "." + raw_body) and comparing to the signature. Reject if the timestamp drifts more than 5 minutes from your clock.
const crypto = require("crypto");
function verifyWebhook(rawBody, headers, secret) {
const timestamp = headers["theo-webhook-timestamp"];
const signature = headers["theo-webhook-signature"];
// Replay protection
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > 300) throw new Error("Timestamp too old");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const [, hash] = signature.split("=");
if (hash !== expected) throw new Error("Invalid signature");
return JSON.parse(rawBody);
}