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_typeFires whenUses threshold_value?
credits.threshold_hitBalance crosses downward from above threshold_value to at-or-below.Required
credits.exhaustedBalance 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 list
Delete
curl -X DELETE -H "Authorization: Bearer sk_live_..." \
     https://www.skillzdrive.com/api/v1/account/webhooks/<id>

# CLI equivalent
skillzdrive webhooks delete <id>