Agent skill
Violet Webhooks Skill
Receive and verify Violet webhooks. Use when setting up Violet (violet.io) webhook handlers, debugging X-Violet-Hmac signature verification, or handling events like ORDER_COMPLETED, ORDER_SHIPPED, MERCHANT_CONNECTED, or TRANSFER_SENT.
Install this skill
npx skills add hookdeck/webhook-skills --skill violet-webhooks
When to Use This Skill
- How do I receive Violet webhooks?
- How do I verify the
X-Violet-Hmacsignature on a Violet webhook? - Why is my Violet webhook signature verification failing?
- How do I handle ORDER_COMPLETED, ORDER_SHIPPED, MERCHANT_CONNECTED or TRANSFER_SENT events?
- Why did Violet pause or disable my webhook?
Verification (core)
Violet sends X-Violet-Hmac: <base64>: the standard, padded base64 of the HMAC-SHA256 of the raw request body bytes, keyed with your app secret (the same secret you use for API calls) used as UTF-8 — not hex- or base64-decoded. Nothing else is signed (no timestamp or id), and the value has no prefix. Check the header's format, then compare the whole value in constant time.
Node:
const crypto = require('crypto');
function verifyVioletSignature(rawBody, header, secret) {
// 44 chars of padded base64: validate before comparing (never Buffer.from(header, 'base64'))
if (typeof header !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(header)) return false;
const expected = Buffer.from(crypto.createHmac('sha256', secret).update(rawBody).digest('base64'));
const received = Buffer.from(header);
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
Python:
import base64, hashlib, hmac, re
def verify_violet_signature(raw_body: bytes, header: str | None, secret: str) -> bool:
if not header or not re.fullmatch(r"[A-Za-z0-9+/]{43}=", header, re.ASCII):
return False
digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
return hmac.compare_digest(header.encode("ascii"), base64.b64encode(digest))
Respond 401 when the header is missing, malformed, or doesn't match. Compare the base64 string exactly: a value whose last character carries stray low bits decodes to the same bytes but is not what Violet sends, and Hookdeck's Violet source rejects it too.
For complete handlers with route wiring, event dispatch, and tests, see:
- examples/express/ (use
express.raw({ type: '*/*' })so the exact bytes are verified for any content type)- examples/nextjs/ (
await request.arrayBuffer())- examples/fastapi/ (
await request.body())
Dispatch on X-Violet-Topic
The body is the entity itself (a full Order, an Offer, a PayoutAccount, a transfer...) with no event field, and all ORDER_* events send an Order. Only the X-Violet-Topic header names the event, so a request without it can't be classified. Parse the body only after verification, and treat every field as untrusted (check types before reading into it).
Common Event Types (Topics)
| Topic | Description |
|---|---|
ORDER_ACCEPTED | Order accepted |
ORDER_UPDATED | Order changed |
ORDER_COMPLETED | Order completed |
ORDER_SHIPPED | Order shipped |
ORDER_DELIVERED | Order delivered |
ORDER_REFUNDED | Order refunded |
ORDER_CANCELLED | Order cancelled |
ORDER_FAILED | Order failed |
MERCHANT_CONNECTED | Merchant connected to your app |
MERCHANT_DISCONNECTED | Merchant disconnected |
MERCHANT_ENABLED / MERCHANT_DISABLED | Merchant enabled or disabled |
MERCHANT_NEEDS_ATTENTION | Merchant health check needs attention |
OFFER_ADDED / OFFER_UPDATED / OFFER_REMOVED / OFFER_DELETED | Offer availability and changes |
PRODUCT_SYNC_COMPLETED / PRODUCT_SYNC_FAILED | Catalog sync results |
TRANSFER_SENT / TRANSFER_FAILED / TRANSFER_REVERSED | Payouts to merchants |
For the full list of 52 events, see references/overview.md and the Violet webhook events docs.
Delivery and Retries
- Respond 2XX within 10 seconds; queue the work and process it afterwards
- A failed event is retried 10 times with back-off over 24 hours, then marked
FAILED - More than 50 failures in 30 minutes pauses the webhook for 1 hour, then 3 hours, then up to 24 hours; if it still fails after three pauses the webhook is
DISABLEDuntil you re-activate it X-Violet-Event-Ididentifies the event: use it for idempotency.X-Violet-Event-Id-Sourcemarks a resend
Environment Variables
VIOLET_WEBHOOK_SECRET=your_violet_app_secret # The app secret, used as UTF-8 bytes
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 violet --path /webhooks/violet
Reference Materials
- references/overview.md - Violet webhook concepts, events, headers, delivery
- references/setup.md - Registering webhooks through the Violet API
- references/verification.md - Signature verification details and gotchas