Skip to content

Webhooks ​

Solo en la nube

Los webhooks están disponibles solo en la versión Cloud.

Los webhooks permiten que ADHDev Cloud envíe eventos de máquina y agente a tu propio endpoint HTTP.

Comportamiento actual importante:

  • la API de webhooks está activa
  • la página dedicada /webhooks del panel no es actualmente una superficie de navegación estándar estable
  • usa la referencia de la REST API como el contrato canónico para las cargas útiles, los eventos y las formas del historial de entrega
  • las acciones de crear / alternar / eliminar / probar webhook son operaciones de cuenta de la nube protegidas por inicio de sesión reciente y requieren un JWT de sesión de panel nuevo, no una clave de API

Crear un webhook ​

Crea el webhook desde una sesión de panel de la nube con la sesión actualmente iniciada. El cuerpo de la solicitud subyacente es:

json
{
  "url": "https://example.com/hooks/adhdev",
  "events": ["agent:status", "agent:approve_request"]
}

Si llamas a los endpoints de webhook que mutan desde una sesión de navegador obsoleta o con una clave de API, el servidor devuelve 403 RECENT_LOGIN_REQUIRED en lugar de aprovisionar el hook.

La respuesta de creación devuelve:

  • el registro del webhook
  • un secret mostrado una vez para la verificación de firma

events es una lista de permitidos, no una lista libre: solo se aceptan los nombres de abajo (más "*") — cualquier otro se descarta silenciosamente. Usa ["*"] para suscribirte a todos los eventos que el servidor despacha, incluidos los más granulares que no se pueden suscribir individualmente.

Eventos ​

Suscribibles individualmente (aceptados en events):

EventoDisparador
daemon:connectEl daemon se conectó al servidor
daemon:disconnectSe perdió la conexión del daemon
agent:statusCambió el estado del agente (limitado, máx. 1 cada 10s por daemon)
agent:approve_requestReservado — está en la lista de permitidos pero el servidor no lo despacha actualmente

Despachados por el servidor pero solo entregados a webhooks suscritos con "*" (no suscribibles individualmente):

EventoDisparador
agent:generating_startedEl agente empezó a generar una respuesta
agent:generating_completedEl agente terminó de generar
agent:waiting_approvalEl agente está esperando aprobación
agent:waiting_choiceEl agente está esperando una selección múltiple
agent:stoppedLa sesión del agente se detuvo
monitor:no_progressEl agente parece estancado sin progreso (alias legado: monitor:long_generating)
webhook:testEntrega de prueba explícita vía POST /webhooks/{id}/test — omite el filtrado de suscripción por completo, se entrega solo al webhook que se está probando

Formato de la carga útil ​

Cada entrega es un cuerpo JSON con tres campos de nivel superior: event, payload y timestamp (cuándo envió el servidor la entrega, en milisegundos epoch). Para los eventos de agente y de monitor (agent:* salvo agent:status, y monitor:no_progress) la carga útil tiene este aspecto:

json
{
  "event": "agent:generating_completed",
  "payload": {
    "daemonId": "8e1f4c2a9b7d3e5f6a0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f",
    "daemonType": "adhdev-daemon",
    "event": "agent:generating_completed",
    "timestamp": 1714000000000,
    "targetSessionId": "9b2d7c4e-1f3a-4b5c-8d6e-0a1b2c3d4e5f",
    "providerType": "claude-cli",
    "providerSessionId": "3f2a9c1e-5b7d-4e8a-9c0f-1a2b3c4d5e6f",
    "workspaceName": "/home/dev/projects/myproject",
    "duration": 42,
    "surfaceHidden": false,
    "muted": false
  },
  "timestamp": 1714000000215
}
CampoCuándo aparece
daemonIdSiempre: la conexión del daemon que informó del evento
event, timestampSiempre: el nombre del evento y cuándo lo observó el daemon (milisegundos epoch)
targetSessionIdCuando el evento se refiere a una sesión de agente concreta; siempre en agent:generating_completed y agent:stopped
providerTypeCuando el daemon puede resolver la sesión. El proveedor del agente, por ejemplo claude-cli o cursor. Si no, el campo no aparece; nunca contiene el tipo de daemon
daemonTypeSiempre. El tipo de daemon que envió el evento, actualmente adhdev-daemon
providerSessionIdCuando el proveedor expone su propio id de conversación
workspaceNameCuando la sesión tiene un espacio de trabajo: el directorio de trabajo de un agente CLI o el espacio de trabajo del IDE
durationSolo en agent:generating_completed, en segundos enteros, cuando el daemon vio empezar el turno
elapsedSecmonitor:no_progress: segundos sin progreso visible
modalMessage, modalButtonsagent:waiting_approval / agent:waiting_choice: el texto de la solicitud de aprobación y las etiquetas de sus botones
surfaceHidden, mutedCuando el daemon aloja la sesión: si está oculta o silenciada en el panel

daemon:connect, daemon:disconnect, agent:status y webhook:test envían cargas útiles propias con otra forma. Aparte del texto de la solicitud de aprobación en modalMessage, la carga útil nunca incluye mensajes de chat, títulos de chat ni respuestas del agente.

Verificación de firma ​

Cada solicitud de webhook incluye:

  • X-ADHDev-Signature
  • X-ADHDev-Event

El formato de la firma es:

text
X-ADHDev-Signature: t=1714000000000,v1=a3f9c2b1...

Verifícala contra el cuerpo de la solicitud en bruto.

js
import crypto from 'crypto'

function verifySignature(body, signature, secret) {
  const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')))
  const timestamp = parts.t
  const received = parts.v1
  const payload = `${timestamp}.${body}`
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(received),
    Buffer.from(expected)
  )
}

// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-adhdev-signature']
  const body = req.body.toString('utf8')
  if (!verifySignature(body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }
  const event = JSON.parse(body)
  // handle event...
  res.sendStatus(200)
})
python
import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    parts = dict(p.split('=', 1) for p in signature.split(','))
    timestamp = parts.get('t', '')
    received = parts.get('v1', '')
    payload = f"{timestamp}.{body.decode('utf-8')}".encode()
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, expected)

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-ADHDev-Signature', '')
    if not verify_signature(request.data, sig, WEBHOOK_SECRET):
        return 'Invalid signature', 401
    event = request.get_json()
    # handle event...
    return '', 200

Tu secreto de webhook se muestra una vez cuando creas el webhook — guárdalo de forma segura.

Endpoints de gestión ​

text
GET    /api/v1/webhooks
POST   /api/v1/webhooks
PATCH  /api/v1/webhooks/{webhookId}
DELETE /api/v1/webhooks/{webhookId}
GET    /api/v1/webhooks/{webhookId}/deliveries?limit=20
POST   /api/v1/webhooks/{webhookId}/test

Los endpoints GET son la superficie de automatización más segura. Los endpoints que mutan (POST, PATCH, DELETE, test) son operaciones de sesión de panel protegidas por inicio de sesión reciente.

Política de reintentos ​

Las entregas fallidas se reintentan hasta 3 veces con backoff exponencial.

Límites de plan ​

PlanMáx. webhooks
Free2
Pro10
Ultra50
EnterpriseIlimitado

La documentación de la nube alojada está aquí. La documentación de código abierto y autoalojada está en el repositorio OSS.