Using OverSkill API webhooks
Get pushed events for generations, deployments, and app users. Registration, the event catalog, signature verification, and testing deliveries.
Webhooks push events to your server the moment they happen — no polling. They're the recommended way to track generations and deployments in production integrations.
Registering a webhook
POST /api/v1/webhooks
X-API-Key: os_…
Content-Type: application/json
{
"url": "https://yourapp.example.com/overskill",
"name": "Production listener",
"events": ["app.generation.completed", "app.generation.failed", "app.generation.blocked"],
"secret": "a-strong-shared-secret-you-generate"
}
Always set a secret — it's what makes signature verification possible. Manage endpoints with GET /api/v1/webhooks, PATCH /api/v1/webhooks/{id}, and DELETE /api/v1/webhooks/{id}.
The event catalog
Generation (the ones most integrations need):
app.generation.started/app.generation.progress/app.generation.completed/app.generation.failedapp.generation.blocked— generation halted by a business rule (for example, the team is out of credits). The payload includesblock_type,balance, andrequired_credits. Don't auto-retry a blocked generation — surface the block to your user (e.g. a top-up prompt) instead.
App lifecycle: app.created, app.updated, app.deleted
Deployments: app.deployment.started, app.deployment.completed, app.deployment.failed, app.rollback
App users: app.user.created, app.user.access_granted, app.user.access_revoked, app.user.access_restored, app.user.tier_upgraded, app.user.tier_downgraded
Team/billing: team.subscription.created, team.subscription.updated, team.subscription.cancelled
Knowledge base: rag.document.created, rag.document.indexed, rag.enabled
Domains: domain.verified
Testing: webhook.test
Verifying signatures
Every delivery carries an X-Overskill-Signature header:
X-Overskill-Signature: t=1753031000,v1=5f8a2c…
tis a Unix timestampv1isHMAC-SHA256(secret, "{t}.{raw_request_body}")in hex
To verify: recompute the HMAC over the timestamp + a dot + the raw request body (before any JSON parsing), compare with a constant-time comparison, and reject deliveries whose timestamp is older than ~5 minutes (replay protection).
Node example:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(header, rawBody, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected); const b = Buffer.from(v1);
return a.length === b.length && timingSafeEqual(a, b);
}
A complete runnable webhook server (Node, zero dependencies) ships in the OverSkill repo at docs/api-examples/node/webhook-server.mjs.
Testing and monitoring deliveries
POST /api/v1/webhooks/{id}/test— sends awebhook.testevent to your endpoint right nowGET /api/v1/webhooks/{id}/deliveries— recent delivery attempts with status codes
Delivery behavior
- Respond with a 2xx quickly (do your processing async). Non-2xx responses and timeouts are retried; 5xx responses trigger retry with backoff.
- Deliveries can occasionally arrive more than once — make your handlers idempotent.
Prefer per-job callbacks?
If you only care about one generation at a time, you can skip endpoint registration entirely: pass callback_url (plus optional metadata for correlation) when you POST /api/v1/generation_queue, and status updates for that job POST straight to your URL.