API reference

Webhooks

Subscribe to events so your systems react the moment syncs, renders, and audits complete.

Event types

EventDescription
sync.completedA source sync finished (scheduled, on-demand, or a catalog push).
feeds.renderedThe feed artifacts were regenerated.
audit.completedAn audit run finished, with the new score and grade.

Managing webhooks

POST/api/v1/webhooks

Register an HTTPS endpoint for one or more events. Requires the write scope. The response includes the signing secret (prefixed whsec_) once — store it immediately; it is never returned again.

ParameterTypeDescription
urlreqstringHTTPS endpoint to deliver events to.
eventsreqstring[]One or more of the event types above.
create.sh
curl -X POST https://useshelfready.com/api/v1/webhooks \
  -H "Authorization: Bearer $SHELFREADY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/shelfready",
    "events": ["sync.completed", "audit.completed"]
  }'
GET/api/v1/webhooks

List registered endpoints (signing secrets are never returned). Requires the read scope.

DELETE/api/v1/webhooks

Remove an endpoint by ID. Requires the write scope.

delete.sh
curl -X DELETE https://useshelfready.com/api/v1/webhooks \
  -H "Authorization: Bearer $SHELFREADY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "id": "9e8d7c6b-5a49-3827-1605-f4e3d2c1b0a9" }'

Deliveries

Every delivery is a JSON POST with a top-level event, created_at, and an event-specific data object. Requests carry X-ShelfReady-Signature and X-ShelfReady-Event headers and the User-Agent ShelfReady-Webhooks/1.0, and time out after 10 seconds — respond with a 2xx quickly and do the work asynchronously.

audit.completed
{
  "event": "audit.completed",
  "created_at": "2026-07-17T09:20:44.000Z",
  "data": {
    "run_id": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0",
    "catalog_score": 72,
    "grade": "B"
  }
}

Verifying signatures

The X-ShelfReady-Signature header has the form t=<unix ts>,v1=<hex HMAC-SHA256>, where the MAC is computed over <ts>.<body> with your whsec_… secret. Verify every delivery and reject stale timestamps.

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto"

export function verifyShelfReady(secret: string, body: string, header: string): boolean {
  // header: "t=<unix ts>,v1=<hex hmac>"
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)))
  const t = Number(parts.t)
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false

  const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest()
  const given = Buffer.from(parts.v1 ?? "", "hex")
  return given.length === expected.length && timingSafeEqual(given, expected)
}

Compute the signature against the exact raw bytes you received. Parsing to JSON first can reorder keys and break verification.

Retries

A delivery that fails (non-2xx, timeout, or connection error) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. After the last failed attempt the delivery is marked dead and not retried again.