developerz.ai can POST signed events to an HTTPS endpoint you own. Use them to mirror work into a tracker, post into a chat channel, or trigger a workflow in n8n — everything the first-party integrations would do, and everything they do not do yet.
Create one
curl -X POST https://api.developerz.ai/v1/webhooks \
-H "Authorization: Bearer dev_pat_…" \
-H "content-type: application/json" \
-d '{"name":"ops","url":"https://example.com/hooks/developerz"}'
Scope: write:webhooks. Names are unique per account.
The response contains the signing secret, once. It is sealed server-side and never returned again — store it when you create the hook, or delete the hook and create a new one.
| Method | Path | Scope |
|---|---|---|
GET |
/v1/webhooks |
read:webhooks |
POST |
/v1/webhooks |
write:webhooks |
DELETE |
/v1/webhooks/:id |
write:webhooks |
POST |
/v1/webhooks/:id/test |
write:webhooks |
GET |
/v1/webhooks/deliveries |
read:webhooks |
GET /v1/webhooks returns the URL masked. The delivery ledger keeps the last
100 attempts, newest first, with the status and a response excerpt.
POST /v1/webhooks/:id/test sends a synthetic test.ping through the real
delivery path, and the attempt lands in the ledger like any other — so a test
proves the whole pipe, not a mock of it.
The envelope
{
"id": "evt_01J8Z6M4Q9T7X2C5N0R3B1D8FK",
"type": "pr.merged",
"occurred_at": "2026-07-27T09:14:03.117Z",
"data": { }
}
id is your idempotency key. It stays the same across every retry of the
same event, so the correct receiver behaviour is: look up id, and if you have
seen it, return 2xx and do nothing.
data carries the event-specific payload. Everything lives under data — there
are no event fields at the top level beside the four above.
Verifying the signature
Two headers ride with every delivery:
X-Developerz-Signature: sha256=<hex>
X-Developerz-Event-Version: 1
The signature is HMAC-SHA256(secret, raw_request_body). Verify against the
raw bytes, before any JSON parse or re-serialization.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
The body we sign is canonicalized (stable key order) before it is sent, so a retry re-sends byte-identical bytes and therefore the same signature. You never need to re-canonicalize anything on your side.
Events that fire today
| Event | When |
|---|---|
pr.merged |
A pull request the agent was watching merged. |
issue.escalated |
The agent escalated an issue to a human instead of acting. |
issue.handoff |
A handoff fired via the webhook_ref in your .maintainer.yml. |
test.ping |
You pressed the test button. Never fires on its own. |
Other event types exist in the schema and are not yet produced. We list only what actually fires; if you subscribe to something else today, nothing will arrive. When more events go live they appear here and in the changelog.
Retries
A delivery succeeds on any 2xx. Anything else — including a timeout or a network error — is a failure and is retried on this ladder:
1s → 5s → 30s → 5m → 30m → 2h → 12h → 24h
That is 9 attempts in total. After the ninth failure the webhook is marked unhealthy and stops being retried; fix the endpoint and create a new hook.
Each attempt has a 10-second timeout, and we store the first 2 KB of your response body in the delivery ledger so you can debug from our side.
Endpoint requirements
- HTTPS or HTTP only. Any other scheme is refused.
- Public hosts only. URLs that resolve to loopback, link-local or RFC 1918 addresses are refused, and the refusal reason is generic on purpose.
That second rule exists because your response body is echoed back to you in the
delivery ledger. Without it, a webhook pointed at 169.254.169.254 would be a
read primitive against internal metadata services. If your endpoint is internal,
put a public reverse proxy in front of it.
Inbound webhooks are a different thing
developerz.ai also receives webhooks — from GitHub, and optionally from Sentry and Linear — to learn that something happened in your systems. That side is configured by connecting the source, not by implementing a receiver, and it is covered in the quickstart.