Guide
Webhooks
Get told when your credits run low - before a call fails in production.
A prepaid API that only signals "you are out of credits" by failing a call has already broken something in your system. Webhooks let you find out while there is still time to top up.
Events
| Event | When it fires |
|---|---|
credits.low | Your balance crosses below 25 credits. Fires once on the call that crosses it, not on every call after. |
credits.exhausted | Your balance reaches zero. Calls are now being refused with 402. |
credits.granted | Credits were added - purchase, monthly allowance or manual adjustment. |
api_key.created | A key was minted on your account. |
api_key.revoked | A key was revoked. |
webhook.test | You pressed Send test. |
Setting one up
- Add an endpoint in your dashboard. The URL must be
https(exceptlocalhostfor testing). - Copy the signing secret - like an API key, it is shown once and stored only as we need it.
- Press Send test and confirm your server returns a 2xx.
What we send
json
POST https://your-server.example.com/hooks/toolbox
X-ToolBox-Event: credits.low
X-ToolBox-Timestamp: 1700000000
X-ToolBox-Signature: sha256=9f86d081884c7d65...
{
"id": "0f9b7c1e-...",
"event": "credits.low",
"createdAt": "2026-08-16T09:15:00+00:00",
"data": {
"balance": 18,
"threshold": 25,
"message": "Your credit balance is running low."
}
}Verifying the signature
Anyone who learns your webhook URL could post fake events at it, so verify every delivery before acting on it. The signature is HMAC-SHA256 over "{timestamp}.{body}" using your signing secret:
javascript
import crypto from "node:crypto";
function verify(req, secret) {
const timestamp = req.headers["x-toolbox-timestamp"];
const signature = req.headers["x-toolbox-signature"];
const body = req.rawBody; // the RAW string, before JSON.parse
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
// Constant-time compare - a plain === leaks the correct signature by timing.
const ok = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
// Reject anything older than five minutes so a captured delivery cannot be replayed.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
return ok && age < 300;
}Delivery behaviour
- Any
2xxcounts as delivered. Everything else is a failure. - We wait up to 10 seconds. Return quickly and do your work asynchronously - a slow endpoint is treated as a failed one.
- After 20 consecutive failures the endpoint is disabled automatically so we stop posting to a dead URL. Any single success resets the counter.
- Every attempt - success or failure - is listed in your dashboard with the status code and duration.
- A webhook failing never affects your API calls. Deliveries are dispatched separately from the call you paid for.