TitanVX Developer Hub
Back Manage webhooks

Webhooks

Webhooks guide

Use webhooks to receive events (billing, quota, deployments) in your own app. TitanVX delivers events as signed HTTPS POST requests, with automatic retries on failures.

Every event includes: idtypecreated_atdata

Contents

  1. Overview
  2. Event types
  3. Payload format
  4. Signature verification
  5. Event filters
  6. Retries & idempotency
  7. Managing endpoints
  8. Testing
  9. Troubleshooting

Overview

A webhook endpoint is an HTTPS URL you own. When an event occurs, TitanVX sends an HTTP POST with a JSON body to your endpoint. Your server should respond with a 2xx status code as quickly as possible.

Delivery headers
  • X-TitanVX-Event: event type string
  • X-TitanVX-Delivery: unique delivery id
  • X-TitanVX-Signature: HMAC signature for verification

Event types

These are the event type values you can subscribe to:

Billing

  • billing.plan.changed

Quota

  • quota.threshold.reached (thresholds: 80%, 100%)
  • quota.request.rejected

Deployments

  • agent.deployment.created
  • agent.deployment.updated
  • agent.deployment.deleted

Webhook management

  • webhook.endpoint.created
  • webhook.endpoint.updated
  • webhook.endpoint.deleted
Live catalog

You can also fetch the live event catalog from the gateway:

GET /api/webhooks/events

Payload format

The webhook body is a JSON object. The data shape depends on the event type.

{
  "id": "0c3b1d5b-1d66-4a16-b2b5-7aa3c2c9b5ab",
  "type": "quota.threshold.reached",
  "created_at": "2026-02-21T22:14:02Z",
  "data": {
    "scope": "daily",
    "threshold": 80,
    "limit": 10000,
    "count": 8123,
    "plan": "builder",
    "window": "2026-02-21"
  }
}
Best practices
  • Always parse JSON as UTF‑8.
  • Use id for idempotency (dedupe repeated deliveries).
  • Do not trust the payload unless signature verification passes.

Signature verification

Each webhook endpoint has a secret (shown once at creation, and when rotated). TitanVX signs each delivery using: HMAC_SHA256(secret, "{timestamp}.{raw_body}").

The header looks like: X-TitanVX-Signature: t=1700000000,v1=<hex>

Python (Flask example)

import hmac, hashlib
from flask import Flask, request, abort

WEBHOOK_SECRET = "YOUR_ENDPOINT_SECRET"

def verify(sig_header: str, body: bytes) -> bool:
    # sig_header: "t=...,v1=..."
    parts = dict(p.split("=", 1) for p in sig_header.split(",") if "=" in p)
    ts = parts.get("t", "")
    v1 = parts.get("v1", "")
    msg = (ts + ".").encode("utf-8") + body
    expected = hmac.new(WEBHOOK_SECRET.encode("utf-8"), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

app = Flask(__name__)

@app.post("/titanvx/webhooks")
def handler():
    sig = request.headers.get("X-TitanVX-Signature", "")
    body = request.get_data()  # raw bytes
    if not verify(sig, body):
        abort(400)
    event = request.get_json()
    # process event["type"], event["data"]
    return {"ok": True}

Node.js (Express example)

import crypto from "crypto";
import express from "express";

const WEBHOOK_SECRET = process.env.TITANVX_WEBHOOK_SECRET;
const app = express();

app.post("/titanvx/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const sig = String(req.header("X-TitanVX-Signature") || "");
  const parts = Object.fromEntries(sig.split(",").map(p => p.split("=", 2)));
  const ts = parts.t || "";
  const v1 = parts.v1 || "";
  const msg = Buffer.concat([Buffer.from(ts + ".", "utf8"), req.body]);
  const expected = crypto.createHmac("sha256", WEBHOOK_SECRET).update(msg).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400);
  const event = JSON.parse(req.body.toString("utf8"));
  return res.json({ ok: true });
});
Replay protection (recommended)

In production, reject requests with old timestamps (e.g. older than 5 minutes) and dedupe on event.id.

Event filters

When you create or update an endpoint you can set events to control delivery.

Retries & idempotency

Deliveries retry when your endpoint returns a non-2xx response or times out. You should treat webhook handlers as at-least-once delivery and implement idempotency.

Retry behavior
  • Any 2xx response marks the delivery succeeded.
  • Non-2xx responses are retried with backoff.
  • Use X-TitanVX-Delivery and event.id to dedupe.

Managing endpoints

Use the Webhooks API (or the UI) to manage endpoints and view deliveries.

POST   /api/webhooks/endpoints        # create (returns secret once)
GET    /api/webhooks/endpoints        # list
PATCH  /api/webhooks/endpoints/{id}   # update events/url/active, rotate secret
DELETE /api/webhooks/endpoints/{id}   # disable (or hard delete with ?hard=true)
GET    /api/webhooks/deliveries       # recent deliveries
POST   /api/webhooks/test             # queue test event
UI

Use the management UI at /developer/webhooks.html.

Testing

Send a test event

POST /api/webhooks/test
{
  "event_type": "webhook.test",
  "data": { "hello": "world" }
}

Dev-only mock deployment event

When enabled on the server (DEV_MOCKS_ENABLED=true), you can emit a mock deployment event:

POST /api/dev/mock-agent-deployment
Headers:
  X-Internal-Secret: <TPS_KEY>

Body:
{
  "user_id": "<supabase_user_id>",
  "action": "updated",
  "deployment_id": "dep_123",
  "data": { "status": "active" }
}

Troubleshooting

Download as PDF: click Download PDF and choose “Save as PDF” in your browser print dialog.