Agent skill
Cloudsmith Webhooks Skill
Receive and verify Cloudsmith webhooks. Use when setting up Cloudsmith webhook handlers, debugging X-Cloudsmith-Signature verification, or handling package events like package.created, package.synced and package.security_scanned.
Install this skill
npx skills add hookdeck/webhook-skills --skill cloudsmith-webhooks
When to Use This Skill
- How do I receive Cloudsmith webhooks?
- How do I verify the
X-Cloudsmith-Signatureheader? - How do I handle
package.created,package.syncedorpackage.security_scannedevents? - Why is my Cloudsmith webhook signature verification failing?
- Why are my Cloudsmith webhook deliveries unsigned?
Setup note
Set an HMAC signature key on the Cloudsmith webhook. Without one, deliveries are unsigned and fail verification.
Verification (core)
Cloudsmith signs the raw request body bytes with HMAC-SHA1, keyed with the webhook's HMAC Signature Key (UTF-8, used as-is), and sends the bare lowercase hex digest (40 characters) in X-Cloudsmith-Signature. There is no prefix and no timestamp; the whole header value is the digest. Validate the format before decoding, then compare in constant time.
Node:
const crypto = require('crypto');
function verify(rawBody /* Buffer */, signature, secret) {
if (!signature || !secret || !/^[0-9a-fA-F]{40}$/.test(signature)) return false;
const expected = crypto.createHmac('sha1', secret).update(rawBody).digest();
const received = Buffer.from(signature, 'hex'); // safe: format validated above
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
Python:
import hashlib, hmac, re
def verify(raw_body: bytes, signature: str | None, secret: str | None) -> bool:
if not signature or not secret or not re.fullmatch(r"[0-9a-fA-F]{40}", signature):
return False
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha1).digest()
return hmac.compare_digest(bytes.fromhex(signature), expected)
Read the body as raw bytes (express.raw({ type: '*/*' }), await request.arrayBuffer(), await request.body()) and parse JSON only after the check. A missing, empty, malformed or mismatching signature gets 401.
For complete handlers with route wiring, event dispatch, and tests, see:
Event Types
Dispatch on the body's meta.event_id. The event is not in a header.
meta.event_id | Triggered when |
|---|---|
ping | You send a test from the webhook's page |
package.created | A package is uploaded |
package.syncing | A package starts syncing |
package.synced | A package is fully synchronised and ready |
package.security_scanned | A security scan finishes |
package.quarantined | A package is quarantined |
package.failed | A package fails to sync |
package.released | A package is released |
package.restored | A package is restored |
package.tags_updated | A package's tags change |
package.deleted | A package is deleted |
meta.event_id is reliably there only for the default JSON Object payload format. The JSON Array format wraps the object in [...], Form Encoded sends payload=<url-encoded JSON>, and a Handlebars template sends whatever it emits. Keep the default format unless you handle the others.
For payload examples, see Cloudsmith webhooks
Important Headers
| Header | Description |
|---|---|
X-Cloudsmith-Signature | Bare hex HMAC-SHA1 of the raw body (only when a signature key is set) |
| Secret Header / Secret Value | Optional custom header you define on the webhook, sent with every delivery |
Environment Variables
CLOUDSMITH_WEBHOOK_SECRET=your_hmac_signature_key # "HMAC Signature Key" of the webhook; it can be replaced but not read back
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 cloudsmith --path /webhooks/cloudsmith
Reference Materials
- references/overview.md - Cloudsmith webhook concepts and events
- references/setup.md - Creating and configuring a webhook
- references/verification.md - Signature verification details