Webhooks
A webhook makes ScriptBunker send a signed POST request to a URL you control when something happens to one of your projects. Manage them in Dashboard → Integrations → Webhooks or with the creator API (webhooks:manage).
Events
| Event | Sent when |
|---|---|
release.published | A release of one of your projects is published, including a project's first release |
script.updated | Project details, visibility, access mode or a distribution/key-acquisition control changed, or a rollback happened |
key.created | A ScriptBunker Key is issued, manually or through the public key page |
key.revoked | One key, or all active keys of a project, were revoked |
key.device_reset | A key's device binding was reset |
script.reported | Someone reported one of your scripts — the reason category only, never the reporter or their message |
comment.created | Someone commented on one of your scripts |
milestone.reached | A project reached an estimated-launch milestone |
Only events that really occur are listed. Webhook payloads never include plaintext keys, device identifiers, IP addresses or tokens; keys appear as maskedKey.
Request
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: ScriptBunker-Webhooks/1
X-ScriptBunker-Event: release.published
X-ScriptBunker-Delivery: 5b0f8b8e-4c1a-4d3e-9a55-6f0f1c1a2b3c
X-ScriptBunker-Timestamp: 1790000000
X-ScriptBunker-Signature: v1=3f1c…
{
"id": "5b0f8b8e-4c1a-4d3e-9a55-6f0f1c1a2b3c",
"event": "release.published",
"createdAt": "2026-09-19T12:00:00.000Z",
"data": {
"script": { "id": "…", "slug": "infinite-yield", "title": "Infinite Yield" },
"release": { "id": "…", "version": "2.5.0" }
}
}
id equals the X-ScriptBunker-Delivery header and is the same for every retry of that delivery, so you can use it to de-duplicate.
Verifying the signature
The signature is v1= followed by the hex HMAC-SHA256 of the string "{timestamp}.{raw request body}", keyed with your webhook's signing secret (whsec_…). Always verify against the raw body bytes, compare in constant time and reject timestamps more than five minutes from now.
The timestamp is part of the signed string, so an old delivery cannot be re-stamped, but a valid delivery replayed inside that five-minute window still verifies — ScriptBunker cannot detect that for you. If replays matter to your receiver, also remember the X-ScriptBunker-Delivery ids you have processed for at least the tolerance window and ignore repeats. Every retry of a delivery carries the same delivery id with a fresh timestamp and signature.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(secret: string, headers: Headers, rawBody: string): boolean {
const timestamp = headers.get('x-scriptbunker-timestamp') ?? ''
const signature = headers.get('x-scriptbunker-signature') ?? ''
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const expected = `v1=${createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')}`
const a = Buffer.from(expected)
const b = Buffer.from(signature)
return a.length === b.length && timingSafeEqual(a, b)
}
Secrets
The signing secret is shown once, when the webhook is created or its secret is rotated. Afterwards only its last four characters are shown so you can tell which one you hold. Rotate secret issues a new secret immediately; the previous secret stops verifying at once, so update your receiver first or accept a short gap.
Delivery, retries and failures
- Respond with any
2xxwithin 8 seconds (10 seconds hard limit per attempt). Redirects are not followed and the response body is never read. - Network errors, timeouts,
408,429and5xxresponses are retried after 1 minute, 5 minutes and 30 minutes — at most 4 attempts in total. Other4xxresponses are treated as permanent and are not retried. - After 10 deliveries in a row exhaust their attempts, the webhook is disabled automatically and you can re-enable it from the dashboard.
- A disabled webhook receives nothing. Test sends one signed
webhook.testevent immediately and never retries. - Delivery is decoupled from the action that caused it: publishing a release, generating a key or any other action succeeds even if your endpoint is down.
- Recent deliveries (event, status, attempts) are visible per webhook and are kept for 30 days.
Destination rules
URLs must use https and a public hostname. ScriptBunker refuses to connect to loopback, private, link-local (cloud metadata), carrier-grade NAT and reserved addresses, single-label internal hostnames and URLs with embedded credentials — checked when you save the URL, again on every send, and against every address the hostname resolves to at connection time. Failed attempts show a generic reason so the delivery log cannot be used to probe networks.