Agent skill
Axiom Webhooks Skill
Receive and authenticate Axiom monitor webhooks. Use when setting up an Axiom Custom webhook notifier endpoint, checking a secret header (Axiom does not sign requests), debugging 401s, or handling monitor notifications with action Open and Closed.
Install this skill
npx skills add hookdeck/webhook-skills --skill axiom-webhooks
Axiom (observability) sends a JSON POST to your URL when a monitor fires or resolves, through a Custom webhook notifier. Requests are not signed. You authenticate them with a secret header you add to the notifier yourself, and your endpoint compares that header with the value you stored.
Use the Custom webhook notifier with a secret header. The plain Webhook notifier cannot send one.
The body is a Go template you write. The default template puts the state in action (Open or Closed) and the monitor data under event. Axiom adds no event-type field of its own.
When to Use This Skill
- How do I receive Axiom monitor webhooks?
- How do I authenticate Axiom Custom webhook notifier requests?
- Does Axiom sign webhook payloads? (No: use a secret header)
- How do I handle
OpenandClosedmonitor notifications? - Why does my Axiom webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyAxiomRequest(headers, {
name = process.env.AXIOM_WEBHOOK_HEADER || 'Authorization',
secret = process.env.AXIOM_WEBHOOK_SECRET,
} = {}) {
if (!secret) return false; // never accept when unconfigured
const received = headers[name.toLowerCase()]; // header name is user-chosen
return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}
- The header name is whatever you typed in the notifier, so read it from
AXIOM_WEBHOOK_HEADER(defaultAuthorization) and the expected value fromAXIOM_WEBHOOK_SECRET. AXIOM_WEBHOOK_SECRETis the exact, whole header value, including anyBearerprefix you typed. Compare it whole: do not strip or parse aBearerprefix.- Python:
hmac.compare_digeston bytes (a.encode(),b.encode()); passingstrraises on non-ASCII. - Missing header or mismatch:
401. Body that isn't a JSON object:400. Handled:200. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it.
A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the credential if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Notification Types
Axiom sends no event field. With the default template, dispatch on action:
action | Sent When |
|---|---|
Open | A match monitor matches, or a threshold monitor triggers |
Closed | A threshold monitor resolves |
If you edit the template, dispatch on whatever field you put in it. The examples and tests use the default template as an illustrative payload.
Environment Variables
AXIOM_WEBHOOK_HEADER=Authorization # header name you set on the notifier
AXIOM_WEBHOOK_SECRET=Bearer your-token # exact, whole header value
Local Development
Axiom can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 axiom --path /webhooks/axiom
Use the printed URL as the Webhook URL in the Custom webhook notifier.
Resources
- references/overview.md - What Axiom webhooks are, the template variables and payload shape
- references/setup.md - Create the Custom webhook notifier with a secret header
- references/verification.md - Header check details, limits, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests