Webhooks
Get notified when your account's credit balance crosses a threshold — so partner systems can alert end-users before service stops.
Why webhooks?
Partners running a master SkillzDrive account on behalf of their users need advance warning when credits are running low. Polling is expensive and laggy; webhooks push a signed notification the moment the balance crosses a threshold you picked.
Event catalog
| event_type | Fires when | Uses threshold_value? |
|---|---|---|
| credits.threshold_hit | Balance crosses downward from above threshold_value to at-or-below. | Required |
| credits.exhausted | Balance hits 0. | Ignored |
One event per crossing
Each subscription fires once per downward crossing. If the balance recovers (e.g. you top up) and crosses the threshold again later, it fires again. It does not fire while the balance stays below the threshold.
Create a subscription
Use the REST API or the CLI. The response contains a signing secret shown exactly once — save it now.
curl -X POST https://www.skillzdrive.com/api/v1/account/webhooks \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"event_type": "credits.threshold_hit",
"threshold_value": 500,
"target_url": "https://example.com/hooks/skillzdrive"
}'Delivery format
Deliveries are HTTP POST with JSON body. Headers:
Headers
POST https://example.com/hooks/skillzdrive
Content-Type: application/json
X-Skillzdrive-Event: credits.threshold_hit
X-Skillzdrive-Signature: sha256=<hmac-sha256 of raw body using your secret, hex>Body
{
"event": "credits.threshold_hit",
"owner_user_id": "00000000-0000-0000-0000-000000000000",
"credits_remaining": 487,
"threshold": 500,
"timestamp": "2026-04-22T03:15:42.000Z"
}Timeouts + delivery semantics
- Delivery timeout: 5 seconds. Slower targets are treated as failed.
- At-most-once: failed deliveries are not retried in v1 (we log the HTTP status in
last_delivery_status). Build your receiver to be idempotent anyway. - No ordering guarantees.
Verify signatures
Compute HMAC-SHA256 of the raw request body using your subscription secret, hex-encode it, prefix with sha256=, and constant-time compare against the X-Skillzdrive-Signature header.
import { createHmac, timingSafeEqual } from "crypto";
function verifySkillzDriveSignature(rawBody, secret, headerValue) {
const expected = "sha256=" +
createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(headerValue);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
// Express example
app.post("/hooks/skillzdrive",
express.raw({ type: "application/json" }),
(req, res) => {
const ok = verifySkillzDriveSignature(
req.body, // raw Buffer
process.env.SKILLZDRIVE_WEBHOOK_SECRET,
req.header("x-skillzdrive-signature") ?? ""
);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
// ...handle event...
res.status(200).end();
}
);Verify the raw body
Signature verification depends on the exact bytes we signed. Parse the header beforeJSON-decoding the body, and feed the untouched
Buffer/bytesto your HMAC. Frameworks that silently re-encode JSON will break signatures.List + delete
List
curl -H "Authorization: Bearer sk_live_..." \
https://www.skillzdrive.com/api/v1/account/webhooks
# CLI equivalent
skillzdrive webhooks listDelete
curl -X DELETE -H "Authorization: Bearer sk_live_..." \
https://www.skillzdrive.com/api/v1/account/webhooks/<id>
# CLI equivalent
skillzdrive webhooks delete <id>