Agent skill
Skovik Webhooks Skill
Receive and verify Skovik webhooks (operations platform API, signed with Standard Webhooks). Use when setting up Skovik webhook handlers, debugging webhook-signature verification, or handling events like users.created, users.updated, export_orders.completed, export_orders.failed, branches.archived, and misc.ping.
Install this skill
npx skills add hookdeck/webhook-skills --skill skovik-webhooks
Skovik is an operations platform whose API exposes users, branches and export orders. You add webhook endpoints in the settings UI, and Skovik delivers each event as a JSON POST signed with Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature. Deliveries are thin notifications: the event name is the top-level type, the related resource is only linked, and data.id is the deduplication key.
When to Use This Skill
- How do I receive Skovik webhooks?
- How do I verify Skovik webhook signatures (
webhook-signature,whsec_secret)? - How do I handle
export_orders.completed,users.createdorbranches.updatedevents? - What does the
misc.pingtest event from the settings UI look like? - Why is my Skovik webhook signature verification failing, or rejecting Hookdeck retries?
Verification (core)
Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. Skovik signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use a Standard Webhooks library (standardwebhooks on npm and PyPI), which compares in constant time. Read the webhook-* headers, and when webhook-id is absent fall back to the svix-* headers (svix-id, svix-timestamp, svix-signature), as Hookdeck does.
const { Webhook } = require('standardwebhooks');
const prefix = req.get('webhook-id') ? 'webhook' : 'svix';
const headers = {
'webhook-id': req.get(`${prefix}-id`),
'webhook-timestamp': req.get(`${prefix}-timestamp`),
'webhook-signature': req.get(`${prefix}-signature`),
};
// rawBody: the raw Buffer/string, never re-serialized JSON
const event = new Webhook(process.env.SKOVIK_WEBHOOK_SECRET).verify(rawBody, headers);
// throws on a bad signature or a timestamp outside the window; returns the parsed body
from standardwebhooks.webhooks import Webhook, WebhookVerificationError
wh = Webhook(os.environ["SKOVIK_WEBHOOK_SECRET"]) # "whsec_..."
event = wh.verify(raw_body, {"webhook-id": id_, "webhook-timestamp": ts, "webhook-signature": sig})
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Each endpoint has its own secret, managed in the settings page. It starts with whsec_, and regenerating it invalidates the old one immediately. Keep the full value in SKOVIK_WEBHOOK_SECRET. Handlers fail closed: with the variable unset they return 500 instead of accepting anything. A missing header or a bad signature returns 400. The raw body is required, so in Express use express.raw({ type: '*/*' }) and parse the body only after the check.
Hookdeck retries and replays hit the 5-minute window
The Standard Webhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Skovik re-signs each attempt with a fresh webhook-timestamp, but Hookdeck retries and replays resend the original timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on data.id instead of relying on the timestamp.
Common Event Types
Ten events are documented, and the list may not be exhaustive. The name is type in the JSON body. The examples handle all of these:
| Event | Triggered When |
|---|---|
users.created | A user is created |
users.updated | Some part of the user object changed |
users.archived | A user is archived |
export_orders.created | An export order is created |
export_orders.completed | An export order completed; fetch it for the export file URL |
export_orders.failed | An export order failed; fetch it and read error_message |
branches.created | A branch is created |
branches.updated | A branch changed |
branches.archived | A branch is archived |
misc.ping | A test sent from the settings UI (signed, never retried, no related resource) |
The related resource is only linked, at data.relationships.related_ (omitted when there is none). Fetch its current state from the links.related URL. See references/overview.md for the payload shape.
Environment Variables
# Signing secret of the webhook endpoint (Standard Webhooks format, starts with whsec_)
SKOVIK_WEBHOOK_SECRET=whsec_xxxxx
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 skovik --path /webhooks/skovik
Add an HTTPS endpoint in Skovik's settings with the tunnel URL, then use the settings UI to send a misc.ping.
Reference Materials
- references/overview.md - Skovik webhook concepts, events, payload shape, delivery and retries
- references/setup.md - Add an endpoint in the settings UI, get the secret, send a test ping
- references/verification.md - Standard Webhooks verification details, header fallback, manual verifier, timestamp window