Webhooks
Cloud Only
Webhooks are available in the Cloud version only.
Webhooks let ADHDev Cloud push machine and agent events to your own HTTP endpoint.
Important current behavior:
- the webhook API is live
- the dedicated
/webhooksdashboard page is not currently a stable standard navigation surface - use the REST API reference as the canonical contract for payloads, events, and delivery-history shapes
- webhook create / toggle / delete / test actions are recent-login protected cloud account operations and require a fresh dashboard session JWT, not an API key
Creating a Webhook
Create the webhook from a currently signed-in cloud dashboard session. The underlying request body is:
{
"url": "https://example.com/hooks/adhdev",
"events": ["agent:generating_completed", "agent:waiting_approval"]
}If you call the mutating webhook endpoints from a stale browser session or with an API key, the server returns 403 RECENT_LOGIN_REQUIRED instead of provisioning the hook.
The create response returns:
- the webhook record
- a
secretshown once for signature verification
Use ["*"] in events to subscribe to every supported event.
Events
| Event | Trigger |
|---|---|
agent:generating_started | Agent started generating a response |
agent:generating_completed | Agent finished generating |
agent:waiting_approval | Agent is waiting for approval |
agent:error | Agent encountered an error |
webhook:test | Test delivery requested explicitly |
Payload Format
{
"event": "agent:generating_completed",
"payload": {
"chatTitle": "Claude Code · myproject",
"ideType": "claude-code",
"duration": 42,
"timestamp": 1714000000000
},
"timestamp": 1714000000000
}Signature Verification
Each webhook request includes:
X-ADHDev-SignatureX-ADHDev-Event
The signature format is:
X-ADHDev-Signature: t=1714000000000,v1=a3f9c2b1...Verify it against the raw request body.
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)
})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 '', 200Your webhook secret is shown once when you create the webhook — store it securely.
Management Endpoints
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}/testGET endpoints are the safest automation surface. The mutating endpoints (POST, PATCH, DELETE, test) are recent-login protected dashboard-session operations.
Retry Policy
Failed deliveries are retried up to 3 times with exponential backoff.
Plan Limits
| Plan | Max Webhooks |
|---|---|
| Free | 2 |
| Pro | 10 |
| Team | 50 |
| Enterprise | Unlimited |
