Guide to Ardoq Webhooks: Features and Best Practices
Ardoq webhooks tell your systems when your architecture model changes: a component is edited, a reference between two components changes, or a tag is added. If you use Ardoq, the enterprise-architecture platform, to model applications, capabilities and their dependencies, webhooks are how those changes reach a CMDB, a service catalogue or a governance workflow without polling Ardoq's API.
This guide covers how Ardoq webhooks are enabled, created and signed, how signing changes the events you receive, how to verify a request, and the practices that keep an Ardoq receiver reliable.
What are Ardoq webhooks?
An Ardoq webhook is an organisation-wide subscription. Ardoq calls its URL with an HTTP POST "whenever something changes in Ardoq", and the URL receives data for every change across the organisation. You can't limit a webhook to particular workspaces or event types, and Ardoq doesn't filter payloads by permissions, so your endpoint receives everything, including confidential fields.
Webhooks aren't on by default. Ardoq enables them for your organisation when you ask, by email or through the chat in its help centre, and only Ardoq admins can create them.
Signing is optional, but it is on by default when you create a webhook, and it changes more than the headers. A signed webhook carries an HMAC-SHA256 signature over a timestamp and the raw body, delivers only component, reference and tag changes, and must use HTTPS with a certificate Ardoq verifies. An unsigned webhook gets a broader set of events, has no signature and has no HTTPS requirement.
Ardoq webhook features
| Feature | Details |
|---|---|
| Availability | Enabled per organisation by Ardoq on request. Admins only |
| Configuration | Preferences > Organization settings > Settings, Webhooks tab |
| Scope | Every change across the organisation, unfiltered by workspace or permissions |
| Signing | Optional, on by default at creation, one-way once added |
| Signature header | X-Ardoq-Signature-256: sha256=<hex digest> |
| Timestamp header | X-Ardoq-Timestamp, Unix seconds |
| Scheme | HMAC-SHA256, hex digest |
| Signed content | <X-Ardoq-Timestamp>.<raw body> |
| Secret | Per webhook, shown once. Used as a UTF-8 string |
| Rotation | Rotate signing secret stops the old secret immediately |
| Transport | HTTPS required for signed webhooks, with the certificate verified |
| Retries, timeout, source IPs | Not documented |
Ardoq's FAQ recommends port 443 "to ensure traffic is sent over an encrypted channel".
Common events
Ardoq keys events by the kind of resource that changed, in the body's resource-type field, and says what happened in event-type.
resource-type | Delivered on | Fires when |
|---|---|---|
component | Signed and unsigned webhooks | A component changes |
reference | Signed and unsigned webhooks | A reference between components changes |
tag | Signed and unsigned webhooks | A tag changes, such as one being added to a component |
field, report, dashboard, survey | Unsigned webhooks only | Those resources change. Ardoq says the unsigned set is "including" these, so it may be wider |
Ardoq's only published example uses "event-type": "create", and the other values aren't listed. Ardoq states that "webhooks do not support deletion events", so a deleted component sends nothing.
Here is Ardoq's tag example, trimmed:
{
"resource-type": "tag",
"resource-id": "cf73fa1e257fdb6a576540e9",
"event-type": "create",
"organization": "a8574eb78e6293348cae2d0f",
"organization-label": "usertesting",
"data": {
"_id": "cf73fa1e257fdb6a576540e9",
"name": "Test-tag",
"components": ["e0a1be808bc264be4ed3fd2c"],
"references": [],
"rootWorkspace": "48114aeb3832a02710bf8f41",
"lastModifiedByEmail": "test@ardoq.com",
"_version": 1,
"ardoq": { "entity-type": "tag" }
}
}
data is the changed resource itself. Its keys mix styles: the example has both lastUpdated and last-updated, and both createdBy and created-by, with the same values. The top-level keys are hyphenated, so in JavaScript read them with brackets, for example event['resource-type'].
Setting up Ardoq webhooks
- Ask Ardoq to enable webhooks for your organisation, if they aren't already.
- As an admin, open the main menu and go to Preferences > Organization settings > Settings, then the Webhooks tab.
- Select Create Webhook and enter a name and an HTTPS destination URL.
- Leave the signing option on. It is on by default.
- Copy the secret when Ardoq shows it and store it server-side, for example as
ARDOQ_WEBHOOK_SECRET. Ardoq shows it once and "will never show it again". - Test the webhook with a small change, such as adding a tag to a component and removing it again.
To sign an existing unsigned webhook, open its menu and select Add signing secret. This needs an HTTPS URL, so a webhook on plain HTTP has to be recreated. Signing can't be removed afterwards: to stop signing, you delete the webhook and create a new unsigned one.
If a secret is lost or exposed, select Rotate signing secret from the webhook's menu. The old secret stops working immediately and the new one is shown once, so deploy it straight away.
If you receive Ardoq webhooks through Hookdeck, create a source with the Ardoq type and use its URL as the destination URL. Keep signing on for the Ardoq webhook (it is on by default) and paste its secret into the source, because Ardoq shows it only once. Remember that Ardoq enables webhooks per organisation, so the Webhooks tab only appears once Ardoq has switched the feature on. The source accepts POST only, which is the method Ardoq uses.
Securing Ardoq webhooks
Read X-Ardoq-Timestamp and X-Ardoq-Signature-256. Build the signed bytes from the timestamp exactly as received, a literal ., and the raw request body. Compute an HMAC-SHA256 of them with the secret as a UTF-8 key, hex-encode it, and compare it with the header's digest in constant time.
const crypto = require('crypto');
const express = require('express');
function verifyArdoqSignature(rawBody, signatureHeader, timestampHeader, secret, toleranceSeconds = 0) {
if (!signatureHeader || !timestampHeader || !secret) return false;
// The header is "sha256=<hex>". Accept a bare digest too.
const digest = signatureHeader.trim().replace(/^sha256=/, '');
if (!/^[0-9a-fA-F]{64}$/.test(digest)) return false; // check before Buffer.from(x, 'hex')
// Optional replay window, off by default. Retries and replays resend the original timestamp.
if (toleranceSeconds > 0) {
if (!/^\d+$/.test(timestampHeader)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestampHeader)) > toleranceSeconds) return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(Buffer.concat([Buffer.from(`${timestampHeader}.`, 'utf8'), rawBody]))
.digest();
const received = Buffer.from(digest, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/ardoq', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.ARDOQ_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
const valid = verifyArdoqSignature(
req.body,
req.get('x-ardoq-signature-256'),
req.get('x-ardoq-timestamp'),
secret
);
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
// Route on event['resource-type'], then event['event-type']. Queue the work and acknowledge.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
import re
import time
def verify_ardoq_signature(raw_body: bytes, signature_header, timestamp_header, secret: str,
tolerance_seconds: int = 0) -> bool:
if not signature_header or not timestamp_header or not secret:
return False
digest = signature_header.strip().removeprefix("sha256=")
if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):
return False
# Optional replay window, off by default.
if tolerance_seconds > 0:
if not (timestamp_header.isascii() and timestamp_header.isdigit()):
return False
if abs(time.time() - int(timestamp_header)) > tolerance_seconds:
return False
signed = timestamp_header.encode("utf-8") + b"." + raw_body
expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(digest.lower().encode("utf-8"), expected.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_ardoq_signature(raw_body, request.headers.get("x-ardoq-signature-256"),
# request.headers.get("x-ardoq-timestamp"),
# os.environ["ARDOQ_WEBHOOK_SECRET"])
Ardoq's own Python, Node.js and Java examples reject any request whose timestamp is more than 5 minutes from your clock, and Ardoq tells you to pick "a budget that covers network transit plus clock skew". The samples above leave that check off unless you pass a tolerance. Hookdeck applies no timestamp tolerance, and its retries and replays resend Ardoq's original X-Ardoq-Timestamp, so a 5-minute window in your own code would reject a legitimate retry that arrives later. Because the timestamp is signed, nobody can swap in a fresh one without the secret.
An unsigned Ardoq webhook sends neither header, so there is nothing to verify. If you also need the broader unsigned event set, receive it on a separate webhook and treat it as unauthenticated.
Make Ardoq webhooks production-ready. Hookdeck Event Gateway verifies the
X-Ardoq-Signature-256header over the timestamp and raw body, stores every component, reference and tag change, and delivers it to your service with retries you control.
Ardoq webhook limitations and pain points
Signing narrows the event set
The Problem: After switching a webhook to signed, report and dashboard changes stop arriving.
Why It Happens: Ardoq delivers only component, reference and tag changes on signed webhooks. Field, report, dashboard and survey changes come only on unsigned webhooks, and signing can't be turned off once it is added.
Workarounds:
- Decide which events you need before you add a secret to an existing webhook.
- If you need both, keep a signed webhook for components, references and tags, and a separate unsigned one for the rest, and treat the unsigned stream as untrusted input.
How Hookdeck Can Help: Hookdeck's Ardoq source verifies signed webhooks. Hookdeck answers Ardoq with a 200 either way, then records a request that fails the check as failed verification, so it never reaches your destination. Point an unsigned webhook at a separate source without verification, and the two streams stay apart.
The secret is shown once, and rotation is immediate
The Problem: Verification fails for every request after someone rotates the secret, or nobody can find the secret for a new receiver.
Why It Happens: Ardoq displays the secret only when it is created. Rotate signing secret invalidates the old one at once, with no overlap period in which both work.
Workarounds:
- Store the secret in your secret manager as part of creating the webhook.
- Plan rotations as a deploy: rotate, then update every receiver straight away, and expect failed verifications in between.
How Hookdeck Can Help: With Hookdeck in front, the secret lives in one place, the Ardoq source. Requests that fail verification during a rotation are still recorded, so you can see what arrived and when.
The whole organisation's changes reach one URL
The Problem: Your endpoint receives changes from workspaces it doesn't care about, including data some users aren't allowed to see.
Why It Happens: A webhook covers the entire organisation and isn't filtered by workspace or permissions.
Workarounds:
- Use only an endpoint you control, as Ardoq advises, and treat the payloads as confidential.
- Filter on
data.rootWorkspaceorresource-typeas soon as the request is verified, and drop what you don't need.
How Hookdeck Can Help: Hookdeck filters can match on body fields such as resource-type and data.rootWorkspace, so each destination receives only the changes it should see.
Delivery behaviour is undocumented
The Problem: You don't know whether a change is resent when your endpoint is down, or how long Ardoq waits for a response.
Why It Happens: Ardoq documents no retry policy, timeout or delivery ID header.
Workarounds:
- Respond with a
200as soon as the request is verified, and process it afterwards. - After an outage, reconcile against Ardoq's API rather than assuming every change arrived.
How Hookdeck Can Help: Hookdeck accepts each delivery as it arrives and stores it, then delivers to your service with retries you configure, so your own downtime doesn't depend on what Ardoq does.
Only create is documented
The Problem: You can't write a complete router for event-type from the docs.
Why It Happens: Ardoq's only example shows create, and the other values aren't listed. There are no deletion events at all.
Workarounds:
- Log and acknowledge any
event-typeyou don't recognise, then add handling once you have seen it. - Detect deletions by reconciling against the API, since no webhook tells you.
How Hookdeck Can Help: Hookdeck keeps the full body of every request, so you can see the event-type values Ardoq actually sends and replay them against your handler while you build it.
Best practices
Verify against the raw body
The signature covers the exact bytes Ardoq sent. In Express, use express.raw({ type: '*/*' }). In Next.js, use Buffer.from(await request.arrayBuffer()). In FastAPI, use await request.body(). Ardoq warns that parsing and re-serialising the JSON "changes the bytes and breaks verification".
Route on resource-type, then event-type
Switch on resource-type first, because that is what decides the shape of data, and then on event-type. Acknowledge anything you don't recognise.
Build your own idempotency key
There is no delivery ID. A key built from resource-id, event-type and data._version is a reasonable start where _version is present. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Ardoq doesn't publish a timeout, so don't make it wait. Verify, persist the event, return 200, and do the work afterwards. See why to process webhooks asynchronously.
Conclusion
Ardoq sends every change in your organisation to one webhook URL. Keep signing on, save the secret when it is shown, and verify X-Ardoq-Signature-256 as a hex HMAC-SHA256 of the timestamp, a dot and the raw body. Expect only component, reference and tag changes on a signed webhook, handle unknown event-type values, and plan for the absence of deletion events and a documented retry policy.
Hookdeck Event Gateway sits in front of your receiver, verifies every signed Ardoq request, and holds each change until your service has processed it.
Get started with Hookdeck for free and handle Ardoq webhooks reliably in minutes.