Agent skill
Qlik Cloud Webhooks Skill
Receive and verify Qlik Cloud webhooks (Qlik's SaaS analytics platform). Use when setting up a Qlik Cloud webhook endpoint, verifying the qlik-signature HMAC-SHA256 header, debugging 401s, or handling events like com.qlik.v1.app.created, com.qlik.v1.app.reload.finished, com.qlik.v1.reload.finished and com.qlik.v1.automation.created.
Install this skill
npx skills add hookdeck/webhook-skills --skill qlik-cloud-webhooks
Qlik Cloud tenant webhooks send a JSON CloudEvent when something happens in the tenant: apps, reloads, automations, users, API keys, data-integration tasks. If you give the webhook a secret, every delivery carries a qlik-signature header: the lowercase hex HMAC-SHA256 of the raw request body, keyed with that secret. There is no prefix, and no timestamp, id or other header is signed.
Hookdeck setup note: Set a secret on the Qlik Cloud webhook. Without one, deliveries are unsigned and fail verification.
When to Use This Skill
- How do I receive Qlik Cloud webhooks?
- How do I verify the
qlik-signatureheader on a Qlik Cloud webhook? - Why does my Qlik Cloud webhook signature verification fail or return 401?
- How do I handle
com.qlik.v1.app.reload.finishedorcom.qlik.v1.automation.createdevents? - Does Qlik Cloud retry failed webhooks? (No retry policy is documented; delivery history is kept 7 days and an event can be resent by hand)
Verification (core)
const crypto = require('crypto');
function verifyQlikSignature(rawBody, signature, secret) { // rawBody: Buffer of the exact bytes received
if (!secret || typeof signature !== 'string') return false;
if (!/^[0-9a-fA-F]{64}$/.test(signature)) return false; // validate before comparing
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signature.toLowerCase(), 'utf8'); // never Buffer.from(sig, 'hex') on unvalidated input
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
- Header:
qlik-signature, the whole value is one bare hex digest (nosha256=prefix, no list). - Signed content: the raw body bytes exactly as received. Never decode and re-encode them, and never
JSON.parsethenJSON.stringifybefore verifying. AGETwith no body is verified over zero bytes. - Key: the secret's UTF-8 bytes, used as-is (not hex- or base64-decoded). The secret is 32 to 64 characters, write-once, and set when you create or edit the webhook.
- Hex is compared case-insensitively: require exactly 64 hex characters, lowercase both sides, then compare in constant time. Python:
hmac.compare_digeston bytes, neverstr. - Missing or empty header, malformed value or mismatch: 401. Read the body raw (
express.raw({ type: '*/*' }),await request.arrayBuffer(),await request.body()) and parse it only after the check. - No timestamp is signed, so there is no replay window to check. Don't invent one from unsigned headers or body fields. Make the handler idempotent on the event
idinstead. - After verifying, treat every field of the parsed body as untrusted: check that the body and each nested field have the expected type before reading into them.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Set a Secret
Set a secret on the Qlik Cloud webhook. Without one, deliveries are unsigned and fail verification.
Without a secret Qlik Cloud sends no qlik-signature header, so every delivery fails verification (and the examples answer 401). Secrets are write-once: to rotate one, edit the webhook and enter a new value. See references/setup.md.
HTTP Methods
Qlik Cloud's docs don't name the HTTP method of a delivery. The examples therefore accept GET, POST, PUT, PATCH and DELETE on the webhook route, verify the signature first for every method, treat a missing body as empty bytes, and acknowledge a verified empty request with 200. Hookdeck's Qlik Cloud source pre-selects POST without managing the method, so if Qlik Cloud uses any other method, also allow that method on the Hookdeck source.
Event Payload and Types
Dispatch on the body's type field (a top-level CloudEvents type):
{
"id": "b347fd0d-1321-417f-91ee-90e96c76389c",
"time": "2018-10-30T07:06:22Z",
"type": "com.qlik.v1.reload.finished",
"source": "com.qlik/reloads",
"specversion": "1.0",
"tenantid": "UkeLb6ICti8OA8f3LueRI5kVNtuHc15f",
"data": { "id": "6a849f7f18ec290e6ceb025d", "appId": "002ff8c5-3c11-4464-a22b-68fb5e7ee5e8", "succeeded": true }
}
Events in the Legacy format have been sent since November 3, 2025 as hybrids that also carry the same value in the legacy eventType; the legacy fields are removed on December 10, 2026, leaving type alone. Older payloads (the help page's example) carry only eventType, so the examples read type and fall back to eventType.
| Event | Sent When |
|---|---|
com.qlik.v1.app.created | An app is created |
com.qlik.v1.app.exported | An app is exported |
com.qlik.v1.app.published | An app is published |
com.qlik.v1.app.deleted | An app is deleted |
com.qlik.v1.app.reload.finished | An app reload finishes |
com.qlik.v1.app.data.updated | An app's data is updated |
com.qlik.v1.reload.finished | A reload finishes (data.succeeded tells the outcome) |
com.qlik.v1.api-key.validation.failed | An API key fails validation |
com.qlik.v1.user.created / com.qlik.v1.user.deleted | A user is created or deleted |
com.qlik.v1.automation.created / .updated / .deleted | An automation is created, updated or deleted |
com.qlik.v1.automation.run.started | An automation run starts |
Qlik documents 22 event types in all; handle a few and log the rest. See references/overview.md.
Environment Variables
QLIK_CLOUD_WEBHOOK_SECRET=your_32_to_64_character_webhook_secret # the secret set on the Qlik Cloud webhook
Local Development
Qlik Cloud can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 qlik-cloud --path /webhooks/qlik-cloud
Use the printed URL as the webhook's endpoint (Qlik Cloud requires HTTPS).
Resources
- references/overview.md - What Qlik Cloud webhooks are, event types and payloads
- references/setup.md - Create a webhook and set its secret
- references/verification.md - Signature check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests