Skip to content

Webhooks

Instead of polling for edit and deploy completion, register an HTTPS endpoint and let Exepad call you.

Event Fires when
app.created An app is created
app.deployed A production deploy finishes successfully
app.unpublished An app is taken offline
app.rolled_back A rollback completes
app.deleted An app is deleted
edit.completed / edit.failed An AI edit (including the initial generation) finishes
file.processed / file.failed A knowledge-base file finishes processing
webhook.test You call the test endpoint
Terminal window
curl -X POST https://api.exepad.com/v1/webhooks \
-H "Authorization: Bearer $EXEPAD_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/exepad",
"events": ["edit.completed", "edit.failed", "app.deployed"],
"description": "CI notifications"
}'

The 201 response includes the signing secret (whsec_…) once — store it. Endpoints must be public HTTPS; requests to private/internal addresses are rejected at registration and at delivery time.

List / get / update / delete GET/PATCH/DELETE /v1/webhooks[/{id}]
Fire a test event POST /v1/webhooks/{id}/test (synchronous — returns the delivery result)
Delivery history GET /v1/webhooks/{id}/deliveries
Retry a delivery POST /v1/webhooks/{id}/deliveries/{delivery_id}/retry
POST <your url>
Content-Type: application/json
User-Agent: Exepad-Webhook/1.0
X-Exepad-Event: edit.completed
X-Exepad-Delivery-ID: 8bdd124f-...
Exepad-Signature: t=1784142000,v1=5f8a...c21e
{
"id": "evt_bb65c8d7dc594cc6bb192e06",
"type": "edit.completed",
"created_at": "2026-07-15T16:02:39Z",
"data": { "app_id": "uqon8zje", "edit_id": "..." }
}

Respond with any 2xx within 10 seconds. Non-2xx responses and timeouts are retried with backoff; a webhook that fails persistently is disabled (visible as status + consecutive_failures on the webhook object).

Exepad-Signature is an HMAC-SHA256 over "{timestamp}.{raw_body}" with your whsec_ secret:

import hmac, hashlib
def verify(secret: str, header: str, raw_body: bytes, tolerance_s: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, given = parts["t"], parts["v1"]
expected = hmac.new(
secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
).hexdigest()
import time
return hmac.compare_digest(expected, given) and abs(time.time() - int(ts)) < tolerance_s

Always verify the signature and the timestamp (replay protection) before trusting a delivery.